Spring 5 WebClient集成内置OAuth2安全特性时触发ClientAuthorizationException[server_error]问题排查
问题描述
我正尝试使用Spring 5 WebClient及其内置的OAuth2安全特性,配置了如下的oauth2WebClient Bean:
@Bean("oauth2WebClient") public WebClient oauth2WebClient(final ReactiveOAuth2AuthorizedClientManager reactiveOAuth2AuthorizedClientManager) { final ServerOAuth2AuthorizedClientExchangeFilterFunction serverOAuth2AuthorizedClientExchangeFilterFunction = new ServerOAuth2AuthorizedClientExchangeFilterFunction(reactiveOAuth2AuthorizedClientManager); serverOAuth2AuthorizedClientExchangeFilterFunction.setDefaultClientRegistrationId("app"); try { KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); keyStore.load(new FileInputStream(new ClassPathResource("app.jks").getFile()), "password".toCharArray()); KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); trustStore.load(new FileInputStream(new ClassPathResource("app.jks").getFile()), "password".toCharArray()); // Set up key manager factory to use our key store KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); keyManagerFactory.init(keyStore, "password".toCharArray()); TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init(trustStore); SslContext sslContext = SslContextBuilder.forClient() .keyManager(keyManagerFactory) .trustManager(trustManagerFactory) .build(); HttpClient httpClient = HttpClient.create() .secure(sslContextSpec -> sslContextSpec.sslContext(sslContext)); ClientHttpConnector connector = new ReactorClientHttpConnector(httpClient); return WebClient.builder() .filter(serverOAuth2AuthorizedClientExchangeFilterFunction) .clientConnector(connector) .build(); } catch (Exception e) { // 建议补充异常处理逻辑 throw new RuntimeException("Failed to configure OAuth2 WebClient with SSL", e); } }
我已确认SSL证书配置正确,且当我手动使用WebClient获取AccessToken并调用服务时可以正常工作;但使用内置OAuth2库时,出现了如下错误:
org.springframework.security.oauth2.client.ClientAuthorizationException: [server_error]
堆栈跟踪信息如下:
reactor.core.publisher.FluxOnAssembly$OnAssemblyException: Error has been observed at the following site(s): |_ checkpoint ⇢ Request to POST <url>[DefaultWebClient] Stack trace: java.lang.Exception: #block terminated with an error
请问我哪里操作出错了?
可能的原因与解决方案
1. 令牌请求未复用自定义SSL配置
你手动调用时能成功,是因为用了自己配置的带SSL上下文的WebClient;但Spring OAuth2内置的令牌获取请求是由ReactiveOAuth2AuthorizedClientManager发起的,它默认使用无自定义SSL配置的默认WebClient,这会导致令牌请求时SSL验证失败,最终抛出server_error。
解决方案:
给ReactiveOAuth2AuthorizedClientManager配置自定义的ReactiveOAuth2AccessTokenResponseClient,让它复用你的SSL配置来请求令牌:
@Bean public ReactiveOAuth2AuthorizedClientManager authorizedClientManager( ClientRegistrationRepository clientRegistrationRepository, ReactiveOAuth2AuthorizedClientService authorizedClientService) throws Exception { // 复用你之前的SSL上下文配置 KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); keyStore.load(new FileInputStream(new ClassPathResource("app.jks").getFile()), "password".toCharArray()); KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); trustStore.load(new FileInputStream(new ClassPathResource("app.jks").getFile()), "password".toCharArray()); KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); keyManagerFactory.init(keyStore, "password".toCharArray()); TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init(trustStore); SslContext sslContext = SslContextBuilder.forClient() .keyManager(keyManagerFactory) .trustManager(trustManagerFactory) .build(); // 构建用于令牌请求的带SSL的WebClient HttpClient tokenHttpClient = HttpClient.create() .secure(sslContextSpec -> sslContextSpec.sslContext(sslContext)); ClientHttpConnector tokenConnector = new ReactorClientHttpConnector(tokenHttpClient); WebClient tokenWebClient = WebClient.builder() .clientConnector(tokenConnector) .build(); // 配置令牌响应客户端 WebClientReactiveClientCredentialsTokenResponseClient tokenResponseClient = new WebClientReactiveClientCredentialsTokenResponseClient(); tokenResponseClient.setWebClient(tokenWebClient); DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager = new DefaultReactiveOAuth2AuthorizedClientManager( clientRegistrationRepository, authorizedClientService); authorizedClientManager.setAuthorizedClientProvider( ClientCredentialsReactiveOAuth2AuthorizedClientProvider.builder() .accessTokenResponseClient(tokenResponseClient) .build()); return authorizedClientManager; }
2. ReactiveOAuth2AuthorizedClientManager配置不完整
如果你的ReactiveOAuth2AuthorizedClientManager是默认自动配置的,它可能没有正确关联你的ClientRegistration(比如client-id为"app"的配置是否正确加载),或者缺少必要的授权提供者配置。
排查步骤:
- 检查
application.yml/application.properties中spring.security.oauth2.client.registration.app下的配置,确保token-uri、client-id、client-secret、scope等参数正确无误; - 确保自定义的
authorizedClientManager明确指定了对应的授权提供者(比如客户端模式的ClientCredentialsReactiveOAuth2AuthorizedClientProvider)。
3. 详细异常信息被隐藏
server_error是比较笼统的错误,你可以开启Spring Security的DEBUG日志,查看令牌请求的详细错误信息:
logging.level.org.springframework.security=DEBUG logging.level.org.springframework.web.reactive.function.client=DEBUG
通过详细日志,你可以看到令牌请求的具体响应内容,比如是否是SSL握手失败、令牌端点返回了具体的错误信息等,这能帮你更快定位问题。
内容的提问来源于stack exchange,提问作者dev75

