从安全的websocket连接中提取客户端X509证书

问题描述:

我想在websocket通信之上创建基于证书的身份验证。 所以我创建了一个WebSocket的serverEndpoint,并成立了客户端身份验证SSL与码头的帮助下,像这样:从安全的websocket连接中提取客户端X509证书

Server server = new Server(); 

//Create SSL ContextFactory with appropriate attributes 
SslContextFactory sslContextFactory = new SslContextFactory(); 
    //Set up keystore path, truststore path, passwords, etc 
    ... 
sslContextFactory.setNeedClientAuth(true); 


//Create the connector 
ServerConnector localhostConnector = new ServerConnector(server, sslContextFactory); 
localhostConnector.setHost(...); 
localhostConnector.setPort(...); 
server.addConnector(localhostConnector); 

//Create ContextHandler 
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); 
context.setContextPath("/example"); 
server.setHandler(context); 

// Initialize the JSR-356 layer and add custom Endpoints 
ServerContainer container = WebSocketServerContainerInitializer.configureContext(context); 
container.addEndpoint(Endpoint1.class); //Annotated class 
container.addEndpoint(Endpoint2.class); 

SSL配置似乎是正确的,因为我可以用SSL连接到不同的端点我写的客户端(错误的证书导致连接终止)。

现在,我想提取客户端证书中包含的信息。我看到了,我可以从一个的SSLSession拿到证书,但我在端点要访问的唯一会话是一个“正常”的会议:

@OnOpen 
@Override 
public void open(final Session session, final EndpointConfig config) 

有莫名其妙的方式存储证书或包含的信息和将它传递给端点?

感谢所有帮助:)

我找到了一个解决方案通过session.getUserPrincipal()获得注册为会议的UserPrincipal客户端访问。

UserPricipal是“会话的认证用户”。您nneed然后到authentiation服务添加到您的ServletContextHandler,如下:

//Create SSL ContextFactory with appropriate attributes 
... 

//Create the connector 
... 

//Create ContextHandler 
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); 
    context.setContextPath("/example"); 

//Add security contraint to the context => authentication 

ConstraintSecurityHandler security = new ConstraintSecurityHandler(); 

Constraint constraint = new Constraint(); 
constraint.setName("auth"); 
constraint.setAuthenticate(true); 
constraint.setRoles(new String[]{"user"}); 

Set<String> knownRoles = new HashSet<String>(); 
knownRoles.add("user"); 

ConstraintMapping mapping = new ConstraintMapping(); 
mapping.setPathSpec("/*"); 
mapping.setConstraint(constraint); 

security.setConstraintMappings(Collections.singletonList(mapping), knownRoles); 
security.setAuthMethod("CLIENT-CERT"); 

LoginService loginService = new HashLoginService(); 
security.setLoginService(loginService); 
security.setAuthenticator(new ClientCertAuthenticator()); 

context.setSecurityHandler(security); 

这样,当客户端连接到WebSocket的端点,安全处理机制确保客户端必须通过身份验证。据我了解,ClientCertAuthenticator将检查客户端请求以提取信息(DN的证书),然后将其传递到LoginService,其中客户端进行身份验证和会话的UserPricipal设置。

这里的问题是,你必须有一个工作的loginService(例如,HashLoginService是一个内存登录服务,使用密码和用户名,JDBCLoginService与数据库一起工作)。对于像我这样的人,只是想从证书中提取所需的信息,然后用这些信息进行身份验证,您可以提供自己的LoginService接口实现。

这里是我做过什么:

在安全处理程序的定义:

LoginService loginService = new CustomLoginService(); 
loginService.setIdentityService(new DefaultIdentityService()); 
security.setLoginService(loginService); 

CustomLoginService类

public class CustomLoginService implements LoginService { 

IdentityService identityService = null; 

@Override 
public String getName() { 
    return ""; 
} 

@Override 
public UserIdentity login(String username, Object credentials) { 
    //you need to return a UserIdentity, which takes as argument: 
    // 1. A Subjet, containing a set of principals, a set of private credentials and a set of public ones (type Object) 
    // 2. A Principal of this Subject 
    // 3. A set of roles (String) 

    LdapPrincipal principal = null; 

    try { 
     principal = new LdapPrincipal(username); 
     //you need to have a Principal. I chose LDAP because it is specifically intended for user identified with a DN. 

    } catch (InvalidNameException e) { 
     e.printStackTrace(); 
    } 

    String[] roles = new String[]{"user"}; 
    return new DefaultUserIdentity(
      new Subject(false, 
       new HashSet<LdapPrincipal>(Arrays.asList(new LdapPrincipal[]{principal})), 
       new HashSet<Object>(Arrays.asList(new Object[]{credentials})), 
       new HashSet<Object>(Arrays.asList(new Object[]{credentials}))), 
      principal, 
      roles); 
} 

@Override 
public boolean validate(UserIdentity user) { 

    return false; 
} 

@Override 
public IdentityService getIdentityService() { 
    return identityService; 
} 

@Override 
public void setIdentityService(IdentityService service) { 
    identityService = service; 
} 

@Override 
public void logout(UserIdentity user) { 

} 

就是这样:)

+0

嘿,你有工作项目样本吗?我正在尝试部署websocket服务器,并且还需要提取客户端证书。我没有运气让你像你所描述的那样工作。 – user1563721