`servletRequest 不能为 null` -> SpringBoot - WebClient - 使用 Keycloak 的 GET 请求

bmr*_*rki 11 webclient httprequest blocking spring-boot

我正在尝试在两个微服务之间发出 GET 请求(使用 Keycloak 身份验证)。

\n

假设微服务 A 正在向微服务 B 请求一些资源。\n微服务 B 有一个 GET 端点,它似乎可以工作,因为在从邮递员或 intelliJ http_client 发出请求时我可以看到正确的响应。

\n

在微服务 AI 中尝试发出请求(我确实尝试发出阻塞和非阻塞请求):

\n
    \n
  • 阻止请求
  • \n
\n
String response = webClient.mutate()\n                .baseUrl(this.serverUri)\n                .build().get()\n                .uri(uriBuilder -> uriBuilder\n                        .path("/users/tokens/{id}")\n                        .build(userId))\n                .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)\n                .attributes(ServerOAuth2AuthorizedClientExchangeFilterFunction\n                        .clientRegistrationId("keycloak"))\n                .retrieve()\n                .bodyToMono(String.class)\n                .doOnError(RuntimeException::new)\n                .block();\n
Run Code Online (Sandbox Code Playgroud)\n
    \n
  • 非阻塞请求:
  • \n
\n
        webClient.mutate()\n                .baseUrl(this.serverUri)\n                .build().get()\n                .uri(uriBuilder -> uriBuilder\n                        .path("/users/tokens/{id}")\n                        .build(userId))\n                .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)\n                .attributes(ServerOAuth2AuthorizedClientExchangeFilterFunction\n                        .clientRegistrationId("keycloak"))\n                .retrieve()\n                .bodyToMono(String.class)\n                .subscribe(resp -> {\n                    JSONObject jsonObject = new JSONObject(resp);\n                    JSONArray jsonArray = jsonObject.getJSONArray("Tokens");\n                    for (int i = 0; i < jsonArray.length(); i++) {\n                        log.info("token :: " + jsonArray.get(i).toString());\n                    }\n                });\n
Run Code Online (Sandbox Code Playgroud)\n

这是我的WebClient配置:

\n
@Configuration\npublic class WebClientConfiguration {\n\n    @Bean\n    public OAuth2AuthorizedClientManager authorizedClientManager(\n            ClientRegistrationRepository clientRegistrationRepository,\n            OAuth2AuthorizedClientRepository authorizedClientRepository) {\n\n        OAuth2AuthorizedClientProvider authorizedClientProvider =\n                OAuth2AuthorizedClientProviderBuilder.builder()\n                        .refreshToken()\n                        .clientCredentials()\n                        .build();\n\n        DefaultOAuth2AuthorizedClientManager authorizedClientManager =\n                new DefaultOAuth2AuthorizedClientManager(\n                        clientRegistrationRepository, authorizedClientRepository);\n        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);\n\n        return authorizedClientManager;\n    }\n\n    @Bean\n    WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {\n        ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =\n                new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);\n        return WebClient.builder()\n                .apply(oauth2Client.oauth2Configuration())\n                .build();\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

我所做的一切尝试都以这种错误结束:

\n
2021-06-29 16:44:07.854 ERROR 390692 --- [oundedElastic-1] reactor.core.publisher.Operators         : Operator called default onErrorDropped\n\nreactor.core.Exceptions$ErrorCallbackNotImplemented: java.lang.IllegalArgumentException: servletRequest cannot be null\nCaused by: java.lang.IllegalArgumentException: servletRequest cannot be null\n    at org.springframework.util.Assert.notNull(Assert.java:201) ~[spring-core-5.3.5.jar:5.3.5]\n    Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: \nError has been observed at the following site(s):\n    |_ checkpoint \xe2\x87\xa2 Request to GET https://localhost:8080/api/users/tokens/94a2d4f7-b372-4e13-aa16-7b244c099721 [DefaultWebClient]\nStack trace:\n        at org.springframework.util.Assert.notNull(Assert.java:201) ~[spring-core-5.3.5.jar:5.3.5]\n        at org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager.authorize(DefaultOAuth2AuthorizedClientManager.java:144) ~[spring-security-oauth2-client-5.4.5.jar:5.4.5]\n        at org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction.lambda$authorizeClient$24(ServletOAuth2AuthorizedClientExchangeFilterFunction.java:552) ~[spring-security-oauth2-client-5.4.5.jar:5.4.5]\n        at reactor.core.publisher.MonoSupplier.call(MonoSupplier.java:85) ~[reactor-core-3.4.4.jar:3.4.4]\n        at reactor.core.publisher.FluxSubscribeOnCallable$CallableSubscribeOnSubscription.run(FluxSubscribeOnCallable.java:227) ~[reactor-core-3.4.4.jar:3.4.4]\n        at reactor.core.scheduler.SchedulerTask.call(SchedulerTask.java:68) [reactor-core-3.4.4.jar:3.4.4]\n        at reactor.core.scheduler.SchedulerTask.call(SchedulerTask.java:28) [reactor-core-3.4.4.jar:3.4.4]\n
Run Code Online (Sandbox Code Playgroud)\n

我错过了什么吗?

\n

[编辑]

\n

按照此处的建议更改 WebClientConfiguration (Spring Security 5 Calling OAuth2 Secured API in Application Runner results in IllegalArgumentException)可以解决问题:

\n
2021-06-29 16:44:07.854 ERROR 390692 --- [oundedElastic-1] reactor.core.publisher.Operators         : Operator called default onErrorDropped\n\nreactor.core.Exceptions$ErrorCallbackNotImplemented: java.lang.IllegalArgumentException: servletRequest cannot be null\nCaused by: java.lang.IllegalArgumentException: servletRequest cannot be null\n    at org.springframework.util.Assert.notNull(Assert.java:201) ~[spring-core-5.3.5.jar:5.3.5]\n    Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: \nError has been observed at the following site(s):\n    |_ checkpoint \xe2\x87\xa2 Request to GET https://localhost:8080/api/users/tokens/94a2d4f7-b372-4e13-aa16-7b244c099721 [DefaultWebClient]\nStack trace:\n        at org.springframework.util.Assert.notNull(Assert.java:201) ~[spring-core-5.3.5.jar:5.3.5]\n        at org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager.authorize(DefaultOAuth2AuthorizedClientManager.java:144) ~[spring-security-oauth2-client-5.4.5.jar:5.4.5]\n        at org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction.lambda$authorizeClient$24(ServletOAuth2AuthorizedClientExchangeFilterFunction.java:552) ~[spring-security-oauth2-client-5.4.5.jar:5.4.5]\n        at reactor.core.publisher.MonoSupplier.call(MonoSupplier.java:85) ~[reactor-core-3.4.4.jar:3.4.4]\n        at reactor.core.publisher.FluxSubscribeOnCallable$CallableSubscribeOnSubscription.run(FluxSubscribeOnCallable.java:227) ~[reactor-core-3.4.4.jar:3.4.4]\n        at reactor.core.scheduler.SchedulerTask.call(SchedulerTask.java:68) [reactor-core-3.4.4.jar:3.4.4]\n        at reactor.core.scheduler.SchedulerTask.call(SchedulerTask.java:28) [reactor-core-3.4.4.jar:3.4.4]\n
Run Code Online (Sandbox Code Playgroud)\n

有人可以解释为什么吗?

\n

小智 18

我不确定您在什么上下文中运行它。但错误消息表明该请求不是在 HttpServletRequest 的上下文中启动的。如果您的请求是由调度程序或类似的程序启动的,则不存在 servletRequest。

Spring 文档说您需要 AuthorizedClientServiceOAuth2AuthorizedClientManager 实现来解决这种情况。

DefaultOAuth2AuthorizedClientManager [...] 在 HttpServletRequest 上下文中使用的 OAuth2AuthorizedClientManager 的默认实现。(在 HttpServletRequest 上下文之外操作时,请改用 AuthorizedClientServiceOAuth2AuthorizedClientManager。)

来自官方文档

AuthorizedClientServiceOAuth2AuthorizedClientManager [...] OAuth2AuthorizedClientManager 的实现,能够在 HttpServletRequest 的上下文之外运行,例如在调度/后台线程和/或服务层中。(在 HttpServletRequest 上下文中操作时,请改用 DefaultOAuth2AuthorizedClientManager。)

来自官方文档