【问题标题】:How to call Oauth2 protected endpoint from a Spring Boot Java Client using WebClient "serverWebExchange cannot be null"如何使用 WebClient“serverWebExchange 不能为空”从 Spring Boot Java 客户端调用 Oauth2 保护端点
【发布时间】:2020-10-15 07:45:48
【问题描述】:

要求是使用 WebClient 从 Java 客户端程序调用 OAuth 保护端点。我正在使用使用反应性对象的密码授予授权类型。请注意,我对反应式编程模式非常陌生。 当我进行 webclient 调用时,我收到以下错误 ** serverWebExchange cannot be null **

请告诉我以下配置和使用是否正确和/或如何初始化 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

版本: 春季启动版本:2.2.6.RELEASE 5.2.2.RELEASE

注意:如https://github.com/jgrandja/spring-security-oauth-5-2-migrate/tree/master/client-app 中所述,我能够验证从 Web 客户端调用受 Oauth 保护的端点

【问题讨论】:

  • 实际上,客户端凭据流最适合不涉及用户的情况

标签: spring-boot oauth-2.0 spring-security-oauth2 spring-webflux spring-webclient


【解决方案1】:

在构建 ReactiveOAuth2AuthorizedClientProvider 时,您不应该使用 authorizationCode 。并且刷新令牌不应该用于机器对机器的通信。

最重要的是不要使用 DefaultReactiveOAuth2AuthorizedClientManager。而是使用

AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager authorizedClientManager =
        new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(
            clientRegistrationRepository, authorizedClientService);
    authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

【讨论】:

    猜你喜欢
    • 2016-11-12
    • 2016-09-28
    • 2018-08-22
    • 2018-09-28
    • 2017-09-26
    • 2021-08-15
    • 1970-01-01
    • 2019-07-09
    • 1970-01-01
    相关资源
    最近更新 更多