Spring Boot单路由TLS客户端证书认证配置异常排查
问题:Spring Boot仅特定路由启用TLS客户端证书认证失败
需实现仅对/receivePDRDataFromIntegrationGAP2/**路由要求TLS客户端证书认证,其余路由后续单独配置。当前配置下该路由返回403;若将client-auth改为need并移除.authenticated(),全站都会要求证书,不符合需求。
SecurityFilterChain配置
@Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { System.out.println("version" + SystemProperties.get("java.version")); http .csrf(csrf -> csrf.disable()) .cors(AbstractHttpConfigurer::disable) .authorizeHttpRequests(auth -> auth.requestMatchers("/receivePDRDataFromIntegrationGAP2/**").authenticated()) .requiresChannel(channel -> channel .requestMatchers("/receivePDRDataFromIntegrationGAP2/**") .requiresSecure() ) .authorizeHttpRequests((authz) -> authz .anyRequest().permitAll() ); return http.build(); }
application.yaml配置
ssl: key-store: gap2-certificate/keystore.jks key-store-password: changeit key-alias: localhost key-password: changeit enabled: true trust-store: gap2-certificate/truststore.jks trust-store-password: changeit client-auth: want
原配置下的Spring Security日志
2023-10-09 15:04:45,708 DEBUG [https-jsse-nio-8082-exec-8] o.s.s.w.FilterChainProxy: Securing POST /receivePDRDataFromIntegrationGAP2/createPDRData?patientFiscalCode=TEST1&doctorFiscalCode=TEST2 2023-10-09 15:04:45,722 DEBUG [https-jsse-nio-8082-exec-8] o.s.s.w.a.c.ChannelProcessingFilter: Request: filter invocation [POST /receivePDRDataFromIntegrationGAP2/createPDRData?patientFiscalCode=TEST1&doctorFiscalCode=TEST2]; ConfigAttributes: [REQUIRES_SECURE_CHANNEL] 2023-10-09 15:04:45,730 DEBUG [https-jsse-nio-8082-exec-8] o.s.s.w.a.AnonymousAuthenticationFilter: Set SecurityContextHolder to anonymous SecurityContext 2023-10-09 15:04:45,736 DEBUG [https-jsse-nio-8082-exec-8] o.s.s.w.a.Http403ForbiddenEntryPoint: Pre-authenticated entry point called. Rejecting access 2023-10-09 15:04:45,740 DEBUG [https-jsse-nio-8082-exec-8] o.s.s.w.FilterChainProxy: Securing POST /error?patientFiscalCode=TEST1&doctorFiscalCode=TEST2 2023-10-09 15:04:45,746 DEBUG [https-jsse-nio-8082-exec-8] o.s.s.w.FilterChainProxy: Secured POST /error?patientFiscalCode=TEST1&doctorFiscalCode=TEST2 2023-10-09 15:04:45,785 DEBUG [https-jsse-nio-8082-exec-8] o.s.s.w.a.AnonymousAuthenticationFilter: Set SecurityContextHolder to anonymous SecurityContext
发送端收到的报错
403 : "{\"timestamp\":\"2023-10-09T13:04:52.401+00:00\",\"status\":403,\"error\":\"Forbidden\",\"path\":\"/api/receivePDRDataFromIntegrationGAP2/createPDRData\"}"
发送端请求代码
// Send a POST request response = restTemplate.postForEntity(uri, requestEntity, String.class);
发送端RestTemplate证书配置
@Bean public RestTemplate restTemplate() throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException { KeyStore clientStore = KeyStore.getInstance("PKCS12"); clientStore.load(new FileInputStream("store/clientBob.p12"), "changeit".toCharArray()); SSLContext sslContext = SSLContextBuilder.create() .setProtocol("TLS") .loadKeyMaterial(clientStore, "changeit".toCharArray()) .loadTrustMaterial(new TrustSelfSignedStrategy()) .build(); SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext); HttpClientConnectionManager connectionManager = PoolingHttpClientConnectionManagerBuilder.create().setSSLSocketFactory(sslConnectionSocketFactory).build(); CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(connectionManager).build(); HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient); return new RestTemplate(requestFactory); }
请求头信息
Request Line: POST https://localhost:8082/api/receivePDRDataFromIntegrationGAP2/createPDRData?patientFiscalCode=TEST1&doctorFiscalCode=TEST2 Request Headers: Accept: [text/plain, application/json, application/*+json, */*] Content-Type: [application/json] Content-Length: [1167] Request Body: {"id":null, some object....}
解决方案
问题核心是当前配置仅要求路由认证,但未启用Spring Security对客户端证书的解析与校验逻辑,导致客户端证书未被识别,请求被判定为匿名访问而拒绝。
步骤1:修改SecurityFilterChain,添加X509认证支持
启用X509认证处理,让Spring Security解析客户端证书并校验合法性:
@Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) .cors(AbstractHttpConfigurer::disable) // 启用X509客户端证书认证 .x509(x509 -> x509 .subjectPrincipalRegex("CN=(.*?)(?:,|$)") // 从证书CN字段提取用户名 .userDetailsService(userDetailsService()) ) .authorizeHttpRequests(auth -> auth .requestMatchers("/receivePDRDataFromIntegrationGAP2/**").authenticated() .anyRequest().permitAll() ) .requiresChannel(channel -> channel .requestMatchers("/receivePDRDataFromIntegrationGAP2/**").requiresSecure() ); return http.build(); } // 自定义用户校验逻辑,实际可替换为从数据库/配置加载信任列表 @Bean public UserDetailsService userDetailsService() { return username -> { // 示例:仅信任CN为指定值的证书 if ("clientBob".equals(username)) { return User.withUsername(username) .password("") .authorities("ROLE_USER") .build(); } throw new UsernameNotFoundException("Unknown certificate: " + username); }; }
步骤2:保持application.yaml的client-auth配置
维持client-auth: want,确保仅目标路由会强制校验证书,其余路由无证书也可访问:
ssl: key-store: gap2-certificate/keystore.jks key-store-password: changeit key-alias: localhost key-password: changeit enabled: true trust-store: gap2-certificate/truststore.jks trust-store-password: changeit client-auth: want
关键注意事项
- 证书信任前置:务必将客户端证书的公钥导入服务端的
truststore.jks,否则SSL层会直接拒绝请求,无法进入Spring Security认证流程。 - 规则顺序:确保目标路由的认证规则在
anyRequest().permitAll()之前,Spring Security按顺序匹配规则。 - 用户校验逻辑:
UserDetailsService需根据实际业务调整,比如校验证书CN是否在信任列表、证书是否过期等。
验证要点
- 检查日志是否出现
X509AuthenticationFilter相关调试信息,确认证书被正确解析。 - 测试非目标路由(如
/test)无需证书即可访问,目标路由必须携带有效证书才能通过认证。
内容的提问来源于stack exchange,提问作者marcus
相关产品推荐
相关产品推荐

