如何配置Tomcat 9,使应用代码可获取客户端证书并自行验证?
Tomcat9 实现仅REST API的客户端证书自定义认证
问题分析
你当前配置clientAuth="want"并指定truststoreFile时,Tomcat会自动验证客户端证书是否在信任库中,无效证书会被直接拦截拒绝,不符合你“让应用代码自主验证”的需求。要实现仅REST API路径的自定义证书验证,需要调整Tomcat配置+应用内Filter拦截的组合方案。
解决方案步骤
1. 修改Tomcat Connector配置
调整server.xml中的SSL Connector,移除truststore相关配置,让Tomcat仅请求客户端证书但不验证有效性,将证书传递给应用处理:
<Connector protocol="org.apache.coyote.http11.Http11NioProtocol" port="8443" maxThreads="200" scheme="https" secure="true" SSLEnabled="true" keyAlias="tthserver" keystoreFile="C:\Temp\keystore\keystore.jks" keystorePass="PW" clientAuth="want" sslProtocol="TLS" />
clientAuth="want":Tomcat会主动请求客户端证书,但不强制要求,无证书的请求也能正常到达应用- 移除
truststoreFile和trustStorePass:避免Tomcat自动验证证书有效性,仅将证书信息传递给应用
2. 应用内添加自定义认证Filter
创建一个Filter,仅拦截REST API路径(比如/api/**),在Filter中获取客户端证书并执行自定义验证逻辑:
编写Filter代码
public class ClientCertAuthFilter implements Filter { private X509TrustManager trustManager; @Override public void init(FilterConfig config) throws ServletException { // 加载自定义信任库,用于验证客户端证书 try { KeyStore trustStore = KeyStore.getInstance("JKS"); trustStore.load(new FileInputStream("C:\\Temp\\keystore\\truststore.jks"), "PW".toCharArray()); TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(trustStore); TrustManager[] trustManagers = tmf.getTrustManagers(); this.trustManager = (X509TrustManager) trustManagers[0]; } catch (Exception e) { throw new ServletException("初始化信任库失败", e); } } @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; // 仅对REST API路径执行证书验证 if (request.getRequestURI().startsWith("/api/")) { X509Certificate[] certs = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate"); // 无证书的情况 if (certs == null || certs.length == 0) { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "需要提供客户端证书"); return; } X509Certificate clientCert = certs[0]; try { // 验证证书是否在信任库中 trustManager.checkClientTrusted(new X509Certificate[]{clientCert}, "RSA"); // 可选:添加额外验证逻辑,比如检查证书有效期、主题DN等 if (clientCert.getNotAfter().before(new Date())) { throw new CertificateException("证书已过期"); } } catch (CertificateException e) { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "无效的客户端证书:" + e.getMessage()); return; } } // 验证通过,继续执行后续请求 chain.doFilter(req, res); } @Override public void destroy() { // 资源清理 } }
配置Filter到web.xml
在应用的web.xml中注册Filter,并指定拦截路径:
<filter> <filter-name>ClientCertAuthFilter</filter-name> <filter-class>com.your.package.ClientCertAuthFilter</filter-class> </filter> <filter-mapping> <filter-name>ClientCertAuthFilter</filter-name> <url-pattern>/api/*</url-pattern> </filter-mapping>
关键说明
- 此方案中,Tomcat仅负责传递证书信息,所有验证逻辑由应用代码控制,完全满足你“仅REST API验证、其他请求不受影响”的需求
- 无证书或无效证书的请求不会被Tomcat拦截,而是由Filter返回401错误,你也可以根据业务需求调整返回逻辑
- 建议将信任库路径、密码等配置提取到环境变量或配置文件中,避免硬编码
内容的提问来源于stack exchange,提问作者Susanne Tietz
相关产品推荐
相关产品推荐

