当前位置: 首页 > 知识库问答 >
问题:

如何使用WebClient从Spring BootJava客户端调用Oauth2受保护的endpoint“ServerWebExchange不能为Null”

黎曾笑
2023-03-14

要求是使用WebClient从java客户端程序调用OAuth保护的endpoint。我使用的是使用反应对象的密码授权类型。请注意,我是反应编程模式的新手。当我调用webclient时,我得到以下错误**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

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

共有1个答案

赵征
2023-03-14

生成ReactiveOAuth2AuthorizedClientProvider时,不应使用authorizationCode。和刷新令牌不应用于计算机到计算机的通信。

最重要的是不要使用DefaultReactiveoAuth2AuthorizedClientManager。取而代之的是使用

AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager authorizedClientManager =
        new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(
            clientRegistrationRepository, authorizedClientService);
    authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
 类似资料:
  • 我正在使用Spring构建Web应用程序。我有多个Spring客户端应用程序和一个OAuth2授权和资源服务器。最终用户首先在客户端应用内进行身份验证,然后客户端应用从资源服务器请求一些资源(从授权服务器获得访问令牌后),处理数据并将其返回给用户。用户也可以更改资源服务器上的数据,但只能通过客户端应用。对于仅使用客户端凭证的资源获取,在这种情况下资源所有者是可信客户端。 在资源服务器上,仅存储客户

  • 出于学习目的,我正在使用OAuth2开发一个REST API Angular 4应用程序。 有些受保护的endpoint只能由经过身份验证的用户调用。让我们以更新用户配置文件的endpoint为例: /users/{user_id} 登录用户将能够通过转到更新配置文件屏幕来更新自己的配置文件,该屏幕在后台将调用 /users/{user_id}发送带有新信息和访问令牌的有效载荷。 我们如何防止恶意

  • 我如何保护我的javascript客户端,你能推荐一些库吗。 提前谢谢你

  • 我们有一个应用程序,它使用Azure Spring Boot Active Directory starter“com.microsoft.Azure:Azure Active Directory Spring Boot starter”和Spring Security来保护对该应用程序的访问。这一切都很好。 这是按照以下说明完成的: https://docs.microsoft.com/en-u

  • 我有一个API,我想与OAuth2安全。我已经用密码做了一个虚拟测试grant_type一切正常。我可以请求令牌,用它访问安全的endpoint,等等。服务器充当授权和资源服务器。 后来我读到,我应该使用隐式的grant_类型,因为客户端将是一个javascript应用程序。 我的客户端是这样配置的: 如果我尝试像这样访问endpoint:http://localhost:8080/oauth/a

  • 快速入门展示了使用 IdentityServer 保护 API 的最基础的场景。 在这个场景中,我们定义一个 API,同时定义一个 想要访问这个 API 的 客户端。客户端将从 IdentityServer 请求获得一个访问令牌,然后用这个令牌来获得 API 的访问权限。 定义 API 范围(Scopes)用来定义系统中你想要保护的资源,比如 API。 由于当前演练中我们使用的是内存配置 —— 添