"CLIENT-CERT" Grails 3.3 中基于 X509 证书的身份验证

"CLIENT-CERT" based X509 certificate authentication in Grails 3.3

一段时间以来,在尝试升级到 Grails 3 时,我一直在尝试镜像我在 Grails 2 中的实现。

我需要使用 "client-cert" 身份验证方法支持基于 X509 证书的身份验证,也就是说,我只想在请求受保护资源后提示我输入证书。 请参阅下面 Application.groovy.

中的当前实施
@Bean
EmbeddedServletContainerCustomizer containerCustomizer() throws Exception {

    return new EmbeddedServletContainerCustomizer() {
        @Override
        public void customize(ConfigurableEmbeddedServletContainer container) {
            TomcatEmbeddedServletContainerFactory tomcat = (TomcatEmbeddedServletContainerFactory) container
            tomcat.addConnectorCustomizers(
                    new TomcatConnectorCustomizer() {
                        @Override
                        public void customize(Connector connector) {
                            connector.setPort(8443)
                            connector.setSecure(true)
                            connector.setScheme("https")

                            Http11NioProtocol proto = (Http11NioProtocol) connector.getProtocolHandler()
                            proto.setMinSpareThreads(5)
                            proto.setSSLEnabled(true)
                            proto.setClientAuth("false")

                            proto.setKeystoreFile("/tmp/keys/app.jks")
                            proto.setKeystorePass("changeit")
                            proto.setKeystoreType("JKS")
                            proto.setKeyAlias("ssl_server")
                            proto.setTruststoreFile("/tmp/keys/app.jts")
                            proto.setTruststoreType("JKS")
                            proto.setTruststorePass("changeit")
                        }
                    })
            tomcat.addContextCustomizers(new TomcatContextCustomizer() {
                @Override
                public void customize(Context context) {
                    context.setPath("/myapp")
                    SecurityConstraint sc = new SecurityConstraint()
                    SecurityCollection securityCollection = new SecurityCollection()
                    securityCollection.setName("Protected")
                    securityCollection.addPattern("/*")

                    sc.addCollection(securityCollection)

                    sc.addAuthRole("mySecureConnection")
                    sc.setUserConstraint("CONFIDENTIAL")
                    context.addConstraint(sc)
                    context.addSecurityRole("mySecureConnection")
                    context.setRealm(new MySecurityRealm())

                    LoginConfig loginConfig = new LoginConfig()
                    loginConfig.setAuthMethod("CLIENT-CERT")
                    loginConfig.setRealmName("MySecurityRealm")
                    context.setLoginConfig(loginConfig)
                    sc.setAuthConstraint(true)

                }
            });
        }
    }

但是无论我尝试使用多少种不同的方式来剪切它,应用程序都不会在访问时请求证书(它应该基于我上面的捕获所有模式)。 请注意,当 clientAuth 设置为 true;

时,此机制确实会按预期工作

proto.setClientAuth("true")

但这意味着总是需要一个证书,这不是我最终想要的(我打算更新上面的模式)。 任何帮助将不胜感激。

在回到 Grails 3 之前,在香草 tomcat 和 Spring 引导级别进行概念验证后,我自己就开始工作了。3.x 应用有效的方法。 我认为拼图中最重要的部分可能是添加了一个 tomcat 阀组件(显然使用 SSLAuthenticator 实现),这是我设法让浏览器提示输入证书的唯一方法。然后,这要求我使用自定义领域从证书中检索主体(目前我不知道其他方法)。 代码如下;

@Bean
public EmbeddedServletContainerFactory servletContainer() {
    final TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory();
    tomcat.addContextValves(new SSLAuthenticator());

    tomcat.addContextCustomizers(new TomcatContextCustomizer() {
        @Override
        public void customize(Context ctx) {

            String AUTH_ROLE = "mySecureRole";
            ctx.addSecurityRole(AUTH_ROLE);
            ctx.setRealm(new MySecurityRealm())

            LoginConfig config = new LoginConfig();
            config.setAuthMethod("CLIENT-CERT");
            config.setRealmName("MySecurityRealm");
            ctx.setLoginConfig(config);

            SecurityConstraint constraint = new SecurityConstraint();
            constraint.addAuthRole(AUTH_ROLE);
            SecurityCollection collection = new SecurityCollection();
            collection.addPattern("/secure");
            constraint.addCollection(collection);
            ctx.addConstraint(constraint);
        }
    })

    tomcat.addAdditionalTomcatConnectors(createConnector());
    return tomcat;
}


private Connector createConnector() {
    Connector connector = new Connector(TomcatEmbeddedServletContainerFactory.DEFAULT_PROTOCOL);

    connector.setPort(8443);
    connector.setSecure(true);
    connector.setScheme("https");

    Http11NioProtocol proto = (Http11NioProtocol) connector.getProtocolHandler();
    proto.setMinSpareThreads(5);
    proto.setSSLEnabled(true);
    proto.setClientAuth("false");
    proto.setSSLProtocol("all");

    proto.setKeystoreFile("/path/store.jks");
    proto.setKeystorePass("changeit");
    proto.setKeystoreType("JKS");
    proto.setKeyAlias("ssl_server");
    proto.setTruststoreFile("/path/store.jts");
    proto.setTruststoreType("JKS");
    proto.setTruststorePass("changeit");

    proto.setSSLVerifyDepth(2);
    return connector;
}

为了完整起见,我将保留连接器的详细信息,但当然,所有重要的事情都发生在上下文定制器中。 现在,当我访问此 Web 应用程序时,系统不会提示我提供证书。这仅在我访问 /secure 路径时发生,这正是我所需要的。