Grails 3.3中基于CLIENT-CERT的X509证书认证迁移问题
迁移Grails 2的CLIENT-CERT X509认证到Grails 3.3(按需提示证书)
我刚好做过类似的迁移,针对你需求的仅访问受保护资源时才提示用户提供证书,在Grails 3.3里需要结合Spring Boot的嵌入式容器配置和Spring Security的X509认证来实现——因为Grails 3基于Spring Boot,和Grails 2的容器配置逻辑有不小差异,具体步骤如下:
1. 配置嵌入式Tomcat的SSL连接器(Application.groovy)
核心是把Tomcat的clientAuth设为want而非true,这样Tomcat不会强制所有HTTPS请求都要求证书,只有当后续Spring Security触发认证时才会提示用户提供证书。
import org.apache.catalina.connector.Connector import org.apache.coyote.http11.Http11NioProtocol import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory class Application { @Bean EmbeddedServletContainerCustomizer containerCustomizer() throws Exception { return new EmbeddedServletContainerCustomizer() { @Override void customize(ConfigurableEmbeddedServletContainer container) { if (container instanceof TomcatEmbeddedServletContainerFactory) { TomcatEmbeddedServletContainerFactory tomcat = (TomcatEmbeddedServletContainerFactory) container; // 创建SSL连接器 Connector sslConnector = new Connector("org.apache.coyote.http11.Http11NioProtocol"); Http11NioProtocol protocol = (Http11NioProtocol) sslConnector.getProtocolHandler(); // 基础HTTPS配置 sslConnector.setScheme("https"); sslConnector.setSecure(true); sslConnector.setPort(8443); // 替换为你的HTTPS端口 // 关键设置:want模式下Tomcat允许无证书请求,但会在需要时提示 sslConnector.setAttribute("clientAuth", "want"); // 证书存储配置(替换为你实际的文件路径和密码) protocol.setSSLEnabled(true); protocol.setKeystoreFile("/path/to/your/keystore.jks"); protocol.setKeystorePass("your-keystore-pass"); protocol.setTruststoreFile("/path/to/your/truststore.jks"); protocol.setTruststorePass("your-truststore-pass"); protocol.setKeyAlias("your-key-alias"); tomcat.addAdditionalTomcatConnectors(sslConnector); } } }; } }
2. 配置Spring Security的X509认证规则
在grails-app/conf/application.groovy中配置Spring Security,指定哪些资源需要证书认证,同时启用X509过滤器:
// 基础Spring Security域类配置(替换为你项目的实际类) grails.plugin.springsecurity.userLookup.userDomainClassName = 'com.yourcompany.User' grails.plugin.springsecurity.userLookup.authorityJoinClassName = 'com.yourcompany.UserRole' grails.plugin.springsecurity.authority.className = 'com.yourcompany.Role' // 启用X509认证 grails.plugin.springsecurity.useX509 = true grails.plugin.springsecurity.x509.filterProcessesUrl = '/login/x509' // 指定自定义的用户详情服务,用于从证书映射到系统用户 grails.plugin.springsecurity.x509.userDetailsService = 'customX509UserDetailsService' // 核心:配置URL拦截规则,仅保护特定路径 grails.plugin.springsecurity.interceptUrlMap = [ '/secure/**': ['ROLE_USER', 'IS_AUTHENTICATED_FULLY'], // 受保护资源,需要证书认证 '/public/**': ['IS_AUTHENTICATED_ANONYMOUSLY'], // 公开资源,无需认证 '/**': ['IS_AUTHENTICATED_ANONYMOUSLY'] // 默认公开 ]
3. 实现自定义X509用户详情服务
创建一个自定义服务,负责从X509证书中提取用户信息,并映射到你的系统用户实体:
// src/main/groovy/com/yourcompany/CustomX509UserDetailsService.groovy import org.springframework.security.core.userdetails.UserDetails import org.springframework.security.core.userdetails.UsernameNotFoundException import org.springframework.security.web.authentication.preauth.x509.X509UserDetailsService import java.security.cert.X509Certificate class CustomX509UserDetailsService implements X509UserDetailsService { @Override UserDetails loadUserDetails(X509Certificate cert) throws UsernameNotFoundException { // 从证书主题DN中提取用户名(示例:从CN字段获取) String subjectDn = cert.getSubjectX500Principal().getName() String username = subjectDn.split("CN=")[1].split(",")[0].trim() // 根据用户名查找系统用户 User user = User.findByUsername(username) if (!user) { throw new UsernameNotFoundException("No user found for certificate CN: ${username}") } // 返回Spring Security的UserDetails对象(包含权限) return new org.springframework.security.core.userdetails.User( user.username, "", // 证书认证无需密码,设为空即可 user.enabled, true, true, true, user.authorities ) } }
关键注意事项
- clientAuth模式差异:
true会强制所有HTTPS请求必须提供证书,want则是允许无证书请求,但在需要认证时触发证书提示,这是实现「按需提示」的核心 - Truststore配置:你的truststore必须包含客户端证书的CA根证书,否则Tomcat会无法验证客户端证书的有效性
- 测试验证:访问公开路径(如
/public/home)不会弹出证书选择框,访问受保护路径(如/secure/dashboard)时才会提示用户选择证书
内容的提问来源于stack exchange,提问作者dre
相关产品推荐
相关产品推荐

