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

时间:2015-05-14 12:43:17

标签: authentication websocket ssl-certificate x509certificate

我想在websocket通信之上创建基于证书的身份验证。 所以我创建了一个websocket serverEndpoint,并在jetty的帮助下为客户端身份验证设置了SSL,如下所示:

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)

有没有办法以某种方式存储证书或包含的信息并将其传递给端点?

感谢您的帮助:)

1 个答案:

答案 0 :(得分:1)

我找到了一个解决方案,让客户注册为会话的UserPrincipal,可由session.getUserPrincipal()访问。

UserPricipal是"会话的认证用户"。然后,您需要为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是一个使用密码和用户名的内存中的Loginservice,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) {

}

就是这样:))