【问题标题】:Extract client X509 certificate from a secured websocket connection从安全的 websocket 连接中提取客户端 X509 证书
【发布时间】:2015-05-14 12:43:16
【问题描述】:

我想在 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)

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

感谢您的帮助:)

【问题讨论】:

    标签: authentication websocket ssl-certificate x509certificate


    【解决方案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。

    这里的问题是你必须有一个有效的登录服务(例如,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) {
    
    }
    

    就是这样:)

    【讨论】:

    • 嘿,你有这方面的工作项目示例吗?我正在尝试部署 websocket 服务器,我还需要提取客户端证书。我没有运气让它像你描述的那样工作。
    猜你喜欢
    • 2014-04-07
    • 2014-04-04
    • 2023-03-03
    • 2023-02-13
    • 1970-01-01
    • 1970-01-01
    • 2012-04-07
    • 1970-01-01
    • 2017-01-21
    相关资源
    最近更新 更多