使用WebClient访问OAuth2 Password授权保护资源遇401问题
之前使用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。
具体修复步骤
修改授权提供者配置
将OAuth2AuthorizedClientProvider改为支持password类型:OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder() .password() // 替换原clientCredentials() .build();补充用户名密码并调整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: 你的密码 # 替换为实际用户密码验证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
相关产品推荐
相关产品推荐

