You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Security 6(OAuth2ResourceServer)调用私有REST请求遇PKIX路径构建失败

解决方案:Spring Boot 3 + Opaque Token资源服务器PKIX证书错误处理

问题核心

PKIX路径构建失败是因为资源服务器调用授权服务器的Token introspection端点(HTTPS)时,JVM不信任对方的SSL证书。Spring Security 6中默认的SpringOpaqueTokenIntrospector使用未配置自定义信任库的RestTemplate,因此即使证书已导入密钥库,也可能未被正确加载使用。

步骤1:确认证书导入有效性

先验证授权服务器的证书是否已正确导入JVM信任库或自定义信任库:

  • 查看默认JVM信任库中的证书:
    keytool -list -keystore $JAVA_HOME/lib/security/cacerts -alias <你的证书别名>
    
    默认密码为changeit,若输出包含目标证书,说明导入成功;若使用自定义信任库,替换路径为你的信任库路径。

步骤2:自定义带SSL信任配置的Opaque Token Introspector

通过自定义RestTemplate并注入到SpringOpaqueTokenIntrospector,确保调用introspection端点时使用指定的信任库:

2.1 构建SSL上下文

创建加载自定义信任库的SSL上下文Bean:

@Bean
public SSLContext sslContext(@Value("${truststore.path}") String truststorePath,
                             @Value("${truststore.password}") String truststorePassword) throws Exception {
    KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
    try (InputStream is = new FileInputStream(truststorePath)) {
        trustStore.load(is, truststorePassword.toCharArray());
    }

    TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    trustManagerFactory.init(trustStore);

    SSLContext sslContext = SSLContext.getInstance("TLS");
    sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
    return sslContext;
}

2.2 配置带SSL的RestTemplate

基于上述SSL上下文构建专用的RestTemplate:

@Bean
public RestTemplate opaqueTokenRestTemplate(SSLContext sslContext) {
    HttpClient httpClient = HttpClientBuilder.create()
            .setSSLContext(sslContext)
            .build();
    HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
    return new RestTemplate(requestFactory);
}

2.3 自定义OpaqueTokenIntrospector

将带SSL的RestTemplate注入到SpringOpaqueTokenIntrospector:

@Bean
public OpaqueTokenIntrospector opaqueTokenIntrospector(RestTemplate opaqueTokenRestTemplate,
                                                      @Value("${auth.server.url}") String introspectionUri,
                                                      @Value("${auth.server.clientId}") String clientId,
                                                      @Value("${auth.server.clientsecret}") String clientSecret) {
    SpringOpaqueTokenIntrospector introspector = new SpringOpaqueTokenIntrospector(introspectionUri, clientId, clientSecret);
    introspector.setRestOperations(opaqueTokenRestTemplate);
    return introspector;
}

步骤3:修复SecurityFilterChain的重复配置问题

代码中重复配置oauth2ResourceServer会导致配置覆盖,合并为一次配置并使用自定义的Introspector:

@Bean
public SecurityFilterChain configure(HttpSecurity http, OpaqueTokenIntrospector opaqueTokenIntrospector) throws Exception {
    http.addFilterAfter(new OncePerRequestFilter() {
        @Override
        protected void doFilterInternal(HttpServletRequest request,
                                        HttpServletResponse response,
                                        FilterChain filterChain)
                throws ServletException, IOException {
            var authHeader = request.getHeader(HEADER_NAME);
            if (StringUtils.isEmpty(authHeader) || !StringUtils.startsWith(authHeader, BEARER_PREFIX)) {
                SecurityContextHolder.clearContext();
            }
            filterChain.doFilter(request, response);
        }
    }, AbstractPreAuthenticatedProcessingFilter.class);

    // 统一配置授权规则
    http.authorizeHttpRequests(auth -> auth
            .requestMatchers("/", "/v1/open/**").permitAll()
            .requestMatchers("/pension/**").authenticated()
            .requestMatchers((request) -> StringUtils.equalsIgnoreCase(activeProfile, "dev"),
                    "/webjars/**", "/resources/**", "/swagger-ui.html", "/swagger-resources/**", "/v2/api-docs", "index.html").permitAll()
            .anyRequest().authenticated());

    // 配置资源服务器使用自定义Introspector
    http.oauth2ResourceServer(oauth2 -> oauth2.opaqueToken(t -> t.introspector(opaqueTokenIntrospector)));

    return http.build();
}

额外排查点

  1. 端点URL正确性:检查auth.server.url是否为授权服务器的Token introspection完整端点(通常类似https://xxx/oauth2/introspect),错误信息中的URLhttps://blabla/dev/agent-auth/oauth/可能路径不完整,导致请求地址错误。
  2. JVM启动参数:若使用默认JVM信任库仍未生效,可通过启动参数强制指定信任库:
    -Djavax.net.ssl.trustStore=/path/to/your/truststore.jks -Djavax.net.ssl.trustStorePassword=your-password
    
  3. 证书类型:确保导入的是授权服务器的根CA证书,而非服务器证书本身,避免证书链不完整。

内容的提问来源于stack exchange,提问作者Muratbek Bauyrzhan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 12:55:54