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

Spring OAuth2体系下客户端通过WebClient调用资源服务器返回401问题排查求助

Spring OAuth2体系下客户端通过WebClient调用资源服务器返回401问题排查求助

各位好,我正在研究Spring新的Authorization Server用法,搭建了三个服务:

  • 端口9090的授权服务器,依赖spring-boot-starter-oauth2-authorization-server
  • 端口8081的资源服务器,依赖spring-boot-starter-oauth2-resource-server
  • 端口8080的客户端,依赖spring-boot-starter-oauth2-client

登录后能正常加载客户端的index.html页面,但调用资源服务器接口时一直返回401错误。我希望不用网关,直接通过WebClient自动获取AccessToken并携带到请求中,麻烦帮忙看看哪里配置漏了?


授权服务器SecurityConfig配置

@Configuration
public class SecurityConfig {

    //An instance of RegisteredClientRepository for managing clients.
    @Bean
    RegisteredClientRepository registeredClientRepository() {
        var clientId = "XXX";
        RegisteredClient clientThymeleaf = RegisteredClient
                .withId(UUID.randomUUID().toString())
                .clientId(clientId)
                .clientSecret("YYY")
                .clientAuthenticationMethods(clientAuthenticationMethods -> clientAuthenticationMethods.addAll(Set.of(
                        ClientAuthenticationMethod.CLIENT_SECRET_BASIC
                )))
                .authorizationGrantTypes(grandTypes -> grandTypes.addAll(Set.of(
                        AuthorizationGrantType.AUTHORIZATION_CODE,
                        AuthorizationGrantType.REFRESH_TOKEN
                )))
                .redirectUri("http://127.0.0.1:8080/login/oauth2/code/spring")
                .postLogoutRedirectUri("http://127.0.0.1:8080/")
                .scopes(scopes -> scopes.addAll(Set.of(
                        "product.read",
                        OidcScopes.OPENID
                )))
                .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
                .build();
        return new InMemoryRegisteredClientRepository(clientThymeleaf);
    }
}

资源服务器配置与代码

YAML配置

spring :
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: http://localhost:9000

ProduitController

@RestController
@RequestMapping("/api/produits")
public class ProduitController {
    private Logger log = LoggerFactory.getLogger(ProduitController.class);

    @Autowired
    private ProduitService produitService;

    @GetMapping
    public ResponseEntity<List<Produit>> listerProduits(Principal principal) {
        log.info("\n** User {} try to get all product.\n", principal.getName());
        List<Produit> produits = produitService.listerProduits();
        return ResponseEntity.ok(produits);
    }
}

ProduitService

@Service
public class ProduitService {
    @Autowired
    private ProduitRepository produitRepository;

    @PreAuthorize("hasAuthority('SCOPE_product.read')")
    public List<Produit> listerProduits() {
        return produitRepository.findAll();
    }
}

客户端配置与代码

YAML配置

spring:
  security:
    oauth2:
      client:
        provider:
          spring:
            issuer-uri: http://localhost:9090
        registration:
          spring:
            provider: spring
            client-id: XXX
            client-secret: YYY
            authorization-grant-type: authorization_code
            client-authentication-method: client_secret_basic
            redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
            scope: product.read, openid

ProduitController

@Controller
public class ProduitController {
    private Logger log = LoggerFactory.getLogger(ProduitController.class);

    @Autowired
    private ProduitService produitService;

    @GetMapping("/produits")
    public String listerProduits(Model model) {
        log.info("\n** Show Page List Products\n");
        Mono<List<Produit>> produits = produitService.getProduits();
        log.info("\n** Lists de produits obtenus\n");
        model.addAttribute("produits", produits);
        return "listeProduits";
    }
}

ProduitService

@Service
public class ProduitService {
    @Value("${ressource-uri.products}")
    private String ressource_products_uri;

    private WebClient webClient;

    public ProduitService(WebClient webClient) {
        this.webClient = webClient;
    }

    public Mono<List<Produit>> getProduits() {
        return webClient
                .get()
                .uri(ressource_products_uri)
                .retrieve()
                .bodyToMono(new ParameterizedTypeReference<List<Produit>>() {
                });
    }
}

WebClient配置

@Configuration
public class WebClientConfiguration {
    @Bean
    WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) {
        ServerOAuth2AuthorizedClientExchangeFilterFunction oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(
                authorizedClientManager);
        oauth.setDefaultOAuth2AuthorizedClient(true);
        // @formatter:off
        return WebClient.builder()
                .filter(oauth)
                .build();
        // @formatter:on
    }

    @Bean
    ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
            ReactiveClientRegistrationRepository clientRegistrationRepository,
            ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
        // @formatter:off
        ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
                ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
                        .authorizationCode()
                        //                        .refreshToken()
                        //                        .clientCredentials()
                        //                        .password()
                        .build();
        // @formatter:on
        DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager = new DefaultReactiveOAuth2AuthorizedClientManager(
                clientRegistrationRepository, authorizedClientRepository);
        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
        return authorizedClientManager;
    }
}

自己梳理的排查方向

我初步想到几个可能的问题点,也想请大家帮忙验证:

  1. 资源服务器Issuer URI不匹配:资源服务器配置里的issuer-uri写的是http://localhost:9000,但我的授权服务器实际跑在9090端口,这应该是笔误吧?是不是要改成http://localhost:9090?
  2. WebClient Token携带问题:虽然设置了oauth.setDefaultOAuth2AuthorizedClient(true),但不确定实际请求是否真的带上了Authorization: Bearer <token>头,有没有简单的方式验证这点?
  3. 权限校验逻辑问题:资源服务器的@PreAuthorize("hasAuthority('SCOPE_product.read')")是否能正确识别JWT里的scope?有没有相关日志可以查看权限校验的详细过程?

备注:内容来源于stack exchange,提问作者Madiagne DIAGNE

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.21 10:29:31