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

使用WebClient访问OAuth2 Password授权保护资源遇401问题

问题:切换OAuth2授权类型为Password后WebClient访问资源返回401 Unauthorized

之前使用OAuth2的client-credentials授权类型时,WebClient可正常访问受保护资源,但切换为Password授权类型后,出现401未授权错误:

401 Unauthorized from GET http://localhost:8086/test2 at org.springframework.web.reactive.function.client.WebClientResponseException.create(WebClientResponseException.java:198) ~[spring-webflux-5.3.19.jar:5.3.19]
    Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
    *__checkpoint ⇢ 401 from GET http://localhost:8086/test2 

已通过Keycloak配置认证服务器,客户端Access Type设为public,且可通过Postman正常获取令牌。


当前配置信息

1. WebSecurity配置(适用于client-credentials,可正常工作)

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("*").permitAll();
    }
}

2. WebClient Bean配置(原client-credentials下正常)

@Configuration
public class WebClientOAuth2Config {
    @Bean("method2")
    WebClient webClientGrantPassword( @Qualifier("authclientmgr2") OAuth2AuthorizedClientManager authorizedClientManager2) {
        ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client2 =
                        new ServletOAuth2AuthorizedClientExchangeFilterFunction(
                        authorizedClientManager2);
        oauth2Client2.setDefaultClientRegistrationId("businesspartners");
        return WebClient.builder().apply(oauth2Client2.oauth2Configuration()).build();
    }

    @Bean("authclientmgr2")
    public OAuth2AuthorizedClientManager authorizedClientManager2(
                    ClientRegistrationRepository clientRegistrationRepository,
                    OAuth2AuthorizedClientRepository authorizedClientRepository) {

        OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
                        .clientCredentials()
                        .build();

        DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
                        clientRegistrationRepository, authorizedClientRepository);
        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

        return authorizedClientManager;
    }
}

3. 访问资源服务器的控制器代码

@RestController
public class Test2Controller {
  @Autowired
  private @Qualifier("method2") WebClient webClient2;

  @GetMapping("/test2")
  public String test2() {
    return webClient2.get().uri("http://localhost:8086/test2")
            .attributes(clientRegistrationId("businesspartners"))
            .retrieve().bodyToMono(String.class).block();
  }
}

4. application.yml配置

server:
  port: 8081

spring:
  security:
    oauth2:
      client:
        registration:
          businesspartners:
            client-id: myclient2
            authorization-grant-type: password
            client-name: johan
            client-secret: password
        provider:
          businesspartners:
            issuer-uri: http://localhost:28080/auth/realms/realm2
            token-uri: http://localhost:28080/auth/realms/realm2/protocol/openid-connect/token

5. Maven依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

解决方案

核心问题

当前OAuth2AuthorizedClientProvider仅配置了clientCredentials(),未支持password授权类型,导致无法获取Password模式的令牌;同时配置中缺少Password模式必需的用户名、密码,且public客户端无需传入client_secret。

具体修复步骤

  1. 修改授权提供者配置
    将OAuth2AuthorizedClientProvider改为支持password类型:

    OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
            .password() // 替换原clientCredentials()
            .build();
    
  2. 补充用户名密码并调整client_secret
    更新application.yml的client配置,添加用户名密码并清空public客户端的secret:

    spring:
      security:
        oauth2:
          client:
            registration:
              businesspartners:
                client-id: myclient2
                authorization-grant-type: password
                client-name: johan
                client-secret: "" # public客户端无需secret,设为空
                username: 你的用户名 # 替换为实际用户账号
                password: 你的密码 # 替换为实际用户密码
    
  3. 验证Keycloak客户端配置
    确保Keycloak客户端的Access Type为public,且开启Direct Access Grants Enabled(Password模式依赖此开关)。

修复后的完整WebClientOAuth2Config

@Configuration
public class WebClientOAuth2Config {
    @Bean("method2")
    WebClient webClientGrantPassword( @Qualifier("authclientmgr2") OAuth2AuthorizedClientManager authorizedClientManager2) {
        ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client2 =
                new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager2);
        oauth2Client2.setDefaultClientRegistrationId("businesspartners");
        return WebClient.builder().apply(oauth2Client2.oauth2Configuration()).build();
    }

    @Bean("authclientmgr2")
    public OAuth2AuthorizedClientManager authorizedClientManager2(
            ClientRegistrationRepository clientRegistrationRepository,
            OAuth2AuthorizedClientRepository authorizedClientRepository) {

        OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
                .password()
                .build();

        DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
                clientRegistrationRepository, authorizedClientRepository);
        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

        // 支持动态传入用户凭证(可选)
        authorizedClientManager.setContextAttributesMapper(contextAttributesMapper());

        return authorizedClientManager;
    }

    private Function<OAuth2AuthorizeRequest, Map<String, Object>> contextAttributesMapper() {
        return authorizeRequest -> {
            Map<String, Object> contextAttributes = new HashMap<>();
            contextAttributes.put(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE,
                    authorizeRequest.getAttribute(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE));
            contextAttributes.put(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE,
                    authorizeRequest.getAttribute(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE));
            return contextAttributes;
        };
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 21:01:37