如何针对特定端点忽略SSL客户端认证(mTLS)
问题:Spring Boot mTLS配置下实现部分API公开访问
背景
应用采用客户端-服务器双向认证(mTLS),核心配置如下:
# application.properties server.ssl.enabled=true server.ssl.protocol=TLS server.ssl.enabled-protocols=TLSv1.3 server.ssl.client-auth=need # ... 以及密钥库相关配置
核心需求
多数API在当前mTLS配置下正常工作,但需将特定API(如/hello)设为公开,无需客户端证书即可访问;其余API(如/secret)仍需强制mTLS认证。
问题现象
尝试通过HttpSecurity配置过滤器链后,无客户端证书的请求访问/hello时直接返回SSL错误,无法达到公开访问的预期。
测试方式
使用curl命令验证:
# 带客户端证书访问(正常返回Hello) curl https://localhost:8080/hello -k -v --cert ./client.cert --key ./client.key # 不带客户端证书访问(返回SSL错误) curl https://localhost:8080/hello -k -v
参数说明:
-k:忽略自签名服务器证书-v:输出详细请求日志--cert/--key:指定客户端证书及密钥文件
已尝试的无效方案
- 将
server.ssl.client-auth设为want或none:所有API均变为公开,不符合部分API需认证的需求; - 配置单条
SecurityFilterChain:仅设置/hello为permitAll(),但mTLS认证在Spring Security过滤器之前的SSL握手阶段就已生效,无证书请求直接被拦截; - 配置两条未指定优先级的
SecurityFilterChain:无法区分公开与需认证的API,仍呈现全公开或全认证的结果。
后续尝试的配置代码:
@Bean SecurityFilterChain publicApiSecurityFilterChain(HttpSecurity http) throws Exception { http.authorizeRequests().antMatchers("/hello").permitAll(); return http.build(); } @Bean SecurityFilterChain privateApiSecurityFilterChain(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/**") .authenticated().and() .csrf().disable() .x509() .subjectPrincipalRegex("CN=(.*?)(?:,|$)") .userDetailsService(userDetailsService()); return http.build(); } @Bean UserDetailsService userDetailsService() { return username -> { if (username == null || username.isEmpty()) { throw new UsernameNotFoundException(username); } return new User(username, "", AuthorityUtils.createAuthorityList("ROLE_SSL_USER")); }; }
版本信息
- Spring Boot:2.7.7(计划迁移至v3)
- Java:17
解决方案
问题根源:当server.ssl.client-auth=need时,SSL握手阶段强制要求客户端证书,请求无法到达Spring Security过滤器链;设为want时,SSL层接受有/无证书的请求,此时可通过Spring Security区分验证逻辑。
1. 修改application.properties配置
将客户端认证模式改为want,允许SSL层接受无证书请求:
server.ssl.client-auth=want
2. 配置带优先级的双SecurityFilterChain
通过@Order注解指定过滤器链执行顺序,确保公开API的规则优先生效:
@Configuration @EnableWebSecurity(debug = true) public class WebSecurityConfig { // 公开API过滤器链,优先级更高 @Bean @Order(1) SecurityFilterChain publicApiFilterChain(HttpSecurity http) throws Exception { http .requestMatchers(matchers -> matchers.antMatchers("/hello")) .authorizeHttpRequests(auth -> auth.anyRequest().permitAll()) .csrf().disable() .cors().disable() .httpBasic().disable() .formLogin().disable(); return http.build(); } // 私有API过滤器链,强制mTLS认证 @Bean @Order(2) SecurityFilterChain privateApiFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth.anyRequest().authenticated()) .csrf().disable() .cors().disable() .httpBasic().disable() .formLogin().disable() .x509(x509 -> x509 .subjectPrincipalRegex("CN=(.*?)(?:,|$)") .userDetailsService(userDetailsService())); return http.build(); } @Bean UserDetailsService userDetailsService() { return username -> { if (username == null || username.isEmpty()) { throw new UsernameNotFoundException(username); } return new User(username, "", AuthorityUtils.createAuthorityList("ROLE_SSL_USER")); }; } }
关键说明
@Order(1):让公开API的过滤器链优先执行,匹配到/hello时直接放行,不进入后续认证逻辑;requestMatchers:限定每条过滤器链仅处理指定路径的请求,避免规则冲突;x509()配置:在私有API的过滤器链中启用X.509证书认证,只有携带有效客户端证书的请求才能通过。
验证效果:
- 访问
/hello:无需客户端证书即可正常返回内容; - 访问
/secret:必须携带有效客户端证书,否则返回401未授权。
内容的提问来源于stack exchange,提问作者Henry Fung
相关产品推荐
相关产品推荐

