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

Spring Cloud Gateway下微服务OAuth2互调NPE问题求助

问题描述

多个微服务部署在Spring Cloud Gateway之后,通过Spring Authorization Server的OAuth2协议实现资源保护,整体运行正常。但部分微服务需要互相调用对方的受保护资源,已为每个微服务在授权服务器注册独立客户端(拥有专属client id和client secret),并在各服务中配置了对应OAuth2客户端,使用WebClient实现跨服务数据获取逻辑。

当从resource-client1通过WebClient调用resource-client2时,出现NullPointerException,错误提示Spring Authorization Server未返回access token,具体错误栈如下:

java.lang.NullPointerException: Cannot invoke "org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse.getAccessToken()" because "tokenResponse" is null
    at org.springframework.security.oauth2.client.ClientCredentialsOAuth2AuthorizedClientProvider.authorize(ClientCredentialsOAuth2AuthorizedClientProvider.java:87) ~[spring-security-oauth2-client-6.0.1.jar:6.0.1]
    Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
    *__checkpoint ⇢ Request to GET http://localhost:8082/hello2 [DefaultWebClient]
Original Stack Trace:
        at org.springframework.security.oauth2.client.ClientCredentialsOAuth2AuthorizedClientProvider.authorize(ClientCredentialsOAuth2AuthorizedClientProvider.java:87) ~[spring-security-oauth2-client-6.0.1.jar:6.0.1]
        at org.springframework.security.oauth2.client.DelegatingOAuth2AuthorizedClientProvider.authorize(DelegatingOAuth2AuthorizedClientProvider.java:71) ~[spring-security-oauth2-client-6.0.1.jar:6.0.1]
        at org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager.authorize(DefaultOAuth2AuthorizedClientManager.java:176) ~[spring-security-oauth2-client-6.0.1.jar:6.0.1]
        at org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction.lambda$authorizeClient$22(ServletOAuth2AuthorizedClientExchangeFilterFunction.java:485) ~[spring-security-oauth2-client-6.0.1.jar:6.0.1]
        at reactor.core.publisher.MonoSupplier.call(MonoSupplier.java:67) ~[reactor-core-3.5.1.jar:3.5.1]

相关配置代码如下:

resource-client1的SecurityConfig

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    SecurityFilterChain defaultFilterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests(authorize -> authorize
                        .anyRequest().authenticated()
                )
                .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
        return http.build();
    }

    @Bean
    public WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
        ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
                new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
        oauth2Client.setDefaultClientRegistrationId("server");

        return WebClient.builder()
                .apply(oauth2Client.oauth2Configuration())
                .build();
    }

    @Bean
    public OAuth2AuthorizedClientManager authorizedClientManager(
            ClientRegistrationRepository clientRegistrationRepository,
            OAuth2AuthorizedClientRepository authorizedClientRepository) {

        OAuth2AuthorizedClientProvider authorizedClientProvider =
                OAuth2AuthorizedClientProviderBuilder.builder()
                        .clientCredentials()
                        .build();
        DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
                clientRegistrationRepository, authorizedClientRepository);
        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

        return authorizedClientManager;
    }
}

resource-client1的application.yml

spring:
  security:
    oauth2:
      client:
        registration:
          server:
            scope: ROLE_RESOURCE_CLIENT1
            client-id: resource-client1
            client-secret: '{noop}secret1'
            client-authentication-method: basic
            authorization-grant-type: client_credentials
            client-name: resource-client1-client-credentials
        provider:
          server:
            token-uri: http://localhost:9001/oauth2/token

      resourceserver:
        jwt:
          issuer-uri: http://localhost:9001

授权服务器的客户端配置

private final static RegisteredClient resource1ServiceClient = RegisteredClient.withId("2")
            .clientId("resource-client1")
            .clientSecret("{noop}secret1")
            .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
            .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
            .build();

private final static RegisteredClient resource2ServiceClient = RegisteredClient.withId("3")
            .clientId("resource-client2")
            .clientSecret("{noop}secret2")
            .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
            .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
            .build();
排查与解决方案

1. 修复客户端Scope配置缺失问题

授权服务器注册客户端时未配置任何scope,但resource-client1的配置中指定了scope: ROLE_RESOURCE_CLIENT1。当客户端请求token时携带授权服务器不存在的scope,会导致服务器拒绝返回token,最终出现tokenResponse为null的NPE。

修改授权服务器的客户端注册代码,添加对应scope:

private final static RegisteredClient resource1ServiceClient = RegisteredClient.withId("2")
        .clientId("resource-client1")
        .clientSecret("{noop}secret1")
        .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
        .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
        .scope("ROLE_RESOURCE_CLIENT1") // 添加对应scope
        .build();

private final static RegisteredClient resource2ServiceClient = RegisteredClient.withId("3")
        .clientId("resource-client2")
        .clientSecret("{noop}secret2")
        .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
        .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
        .scope("ROLE_RESOURCE_CLIENT2") // 根据resource-client2的配置添加对应scope
        .build();

2. 验证Token端点可达性

用curl直接测试resource-client1是否能正常获取token:

curl -u resource-client1:secret1 http://localhost:9001/oauth2/token -d "grant_type=client_credentials&scope=ROLE_RESOURCE_CLIENT1"
  • 若返回包含access_token的JSON响应,说明端点正常;
  • 若返回401,检查client-id/secret是否与授权服务器配置一致;
  • 若返回400,检查请求参数是否正确,特别是scope是否匹配。

3. 确认WebClient客户端标识匹配

确保oauth2Client.setDefaultClientRegistrationId("server")中的server,与application.yml里spring.security.oauth2.client.registration下的客户端key完全一致,当前配置已匹配,多客户端场景需注意对应关系。

4. 可选:配置JWT权限注入(针对资源服务器授权)

如果resource-client2需要验证JWT中的权限,需在授权服务器添加TokenCustomizer,将客户端scope注入JWT:

@Bean
public OAuth2TokenCustomizer<JwtEncodingContext> jwtTokenCustomizer() {
    return context -> {
        if (AuthorizationGrantType.CLIENT_CREDENTIALS.equals(context.getAuthorizationGrantType())) {
            Set<String> scopes = context.getRegisteredClient().getScopes();
            context.getClaims().claim("roles", scopes);
        }
    };
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 11:15:38