spring-boot - 如何使用 WebClient“serverWebExchange 不能为空”从 Spring Boot Java 客户端调用 Oauth2 保护端点
问题描述
要求是使用 WebClient 从 java 客户端程序调用 OAuth 保护端点。我正在使用使用反应性对象的密码授予授权类型。请注意,我对反应式编程模式非常陌生。当我进行 webclient 调用时,我收到以下错误 ** serverWebExchange 不能为空 **
请让我知道以下配置和使用是否正确和/或如何初始化 serverWebExchange 对象。
String data = webClient
.post().uri(endPoint)
// This will add the Authorization header with the bearer token.
.attributes(ServerOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId("custom"))
.body(Mono.just(alert), Alert.class)
.retrieve()
.bodyToMono(String.class)
// Block until we receive a response for a non-reactive client.
.block();
java.lang.IllegalArgumentException: serverWebExchange cannot be null
at org.springframework.security.oauth2.client.web.DefaultReactiveOAuth2AuthorizedClientManager.lambda$loadAuthorizedClient$9(DefaultReactiveOAuth2AuthorizedClientManager.java:102)
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
|_ checkpoint ⇢ Request to POST http://<hostname>/<protected_endpoint> [DefaultWebClient]
Stack trace:
at org.springframework.security.oauth2.client.web.DefaultReactiveOAuth2AuthorizedClientManager.lambda$loadAuthorizedClient$9(DefaultReactiveOAuth2AuthorizedClientManager.java:102)
at reactor.core.publisher.MonoErrorSupplied.subscribe(MonoErrorSupplied.java:70)
at reactor.core.publisher.Mono.subscribe(Mono.java:4210)
at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onComplete(FluxSwitchIfEmpty.java:75)
.....
at reactor.core.publisher.Mono.block(Mono.java:1665)
at com.xxxxx.oauthclient.ApplicationTests.testWebClient(ApplicationTests.java:41)
....
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:191)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:128)
at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:69)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
Suppressed: java.lang.Exception: #block terminated with an error
at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:99)
at reactor.core.publisher.Mono.block(Mono.java:1666)
at com.xxxx.oauthclient.ApplicationTests.testWebClient(ApplicationTests.java:41)
....
我使用了以下配置
@Configuration
public class ReactiveOAuthConfig {
@Value("${oauth.username}") String username;
@Value("${oauth.password}") String password;
@Bean
WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) {
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth =
new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
return WebClient.builder()
.filter(oauth)
.build();
}
@Bean
ReactiveOAuth2AuthorizedClientManager reactiveOAuth2AuthorizedClientManager(
ReactiveClientRegistrationRepository clientRegistrationRepository,
ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
.authorizationCode()
.refreshToken()
.password()
.build();
DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager =
new DefaultReactiveOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
// For the `password` grant, the `username` and `password` are supplied via request parameters,
// so map it to `OAuth2AuthorizationContext.getAttributes()`.
authorizedClientManager.setContextAttributesMapper(contextAttributesMapper());
return authorizedClientManager;
}
private Function<OAuth2AuthorizeRequest, Mono<Map<String, Object>>> contextAttributesMapper() {
return authorizeRequest -> {
Map<String, Object> contextAttributes = Collections.emptyMap();
if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
contextAttributes = new HashMap<>();
// `PasswordOAuth2AuthorizedClientProvider` requires both attributes
contextAttributes.put(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, username);
contextAttributes.put(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, password);
}
return Mono.just(contextAttributes);
};
}
应用程序属性看起来像这样
spring.security.oauth2.client.provider.custom.token-uri=https://endpoint_to_get_token
spring.security.oauth2.client.registration.custom.client-id=<clientid>
spring.security.oauth2.client.registration.custom.client-secret=<client-secret>
spring.security.oauth2.client.registration.custom.client-authentication-method=post
spring.security.oauth2.client.registration.custom.authorization-grant-type=password
spring.security.oauth2.client.registration.custom.scope=AppIdClaimsTrust
版本:spring boot 版本:2.2.6.RELEASE <spring-security.version>5.2.2.RELEASE</spring-security.version>
注意:我能够验证从 Web 客户端调用 Oauth 保护端点,如本https://github.com/jgrandja/spring-security-oauth-5-2-migrate/tree/master/client-app中所述
解决方案
在构建ReactiveOAuth2AuthorizedClientProvider时,您不应该使用 authorizationCode 。并且刷新令牌不应该用于机器对机器的通信。
最重要的是不要使用DefaultReactiveOAuth2AuthorizedClientManager。而是使用
AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager authorizedClientManager =
new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientService);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
推荐阅读
- r - 如何在闪亮的应用程序中呈现 ggplot 标题中的多个值?
- javascript - 用于“react-markdown”npm库的.md文件导入的Webpack加载器?
- php - PHP nohup 进程丢失
- excel - 如何在不使用 ComObject 的情况下将列值从 excel 复制到 csv 文件
- javascript - CSS 缩放的页面外观与用户缩放的页面外观不同
- rust - 在不使用 libtest 的情况下使用 `test::TestDescAndFn` 的最佳方法是什么?
- django - Docker 在 Gitlab CI/CD 中提取的图像无法识别 django 测试
- javascript - 有没有办法在“while”事件中等待 5 秒然后执行下一行 javascript?
- powershell - 在 Powershell 中检索 CPU 详细信息
- javascript - 使用参数将 onlick 事件添加到多个元素