Spring Security 5 在 Application Runner 中调用安全的 OAuth2 API 导致 IllegalArgumentException
Spring Security 5 Calling OAuth2 Secured API in Application Runner results in IllegalArgumentException
给定以下代码,是否可以在应用程序运行器中调用受保护的 API 客户端凭据?
@Bean
public ApplicationRunner test(
WebClient.Builder builder,
ClientRegistrationRepository clientRegistrationRepo,
OAuth2AuthorizedClientRepository authorizedClient) {
return args -> {
try {
var oauth2 =
new ServletOAuth2AuthorizedClientExchangeFilterFunction(
clientRegistrationRepo,
authorizedClient);
oauth2.setDefaultClientRegistrationId("test");
var response = builder
.apply(oauth2.oauth2Configuration())
.build()
.get()
.uri("test")
.retrieve()
.bodyToMono(String.class)
.block();
log.info("Response - {}", response);
} catch (Exception e) {
log.error("Failed to call test.", e);
}
};
}
代码失败是因为,
java.lang.IllegalArgumentException: request cannot be null
全栈,
java.lang.IllegalArgumentException: request cannot be null
at org.springframework.util.Assert.notNull(Assert.java:198) ~[spring-core-5.1.5.RELEASE.jar:5.1.5.RELEASE]
at org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizedClientRepository.loadAuthorizedClient(HttpSessionOAuth2AuthorizedClientRepository.java:47) ~[spring-security-oauth2-client-5.1.4.RELEASE.jar:5.1.4.RELEASE]
at org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction.populateDefaultOAuth2AuthorizedClient(ServletOAuth2AuthorizedClientExchangeFilterFunction.java:364) ~[spring-security-oauth2-client-5.1.4.RELEASE.jar:5.1.4.RELEASE]
at org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction.lambda$null(ServletOAuth2AuthorizedClientExchangeFilterFunction.java:209) ~[spring-security-oauth2-client-5.1.4.RELEASE.jar:5.1.4.RELEASE]
at org.springframework.web.reactive.function.client.DefaultWebClient$DefaultRequestBodyUriSpec.attributes(DefaultWebClient.java:234) ~[spring-webflux-5.1.5.RELEASE.jar:5.1.5.RELEASE]
at org.springframework.web.reactive.function.client.DefaultWebClient$DefaultRequestBodyUriSpec.attributes(DefaultWebClient.java:153) ~[spring-webflux-5.1.5.RELEASE.jar:5.1.5.RELEASE]
失败的方法看起来像,
public <T extends OAuth2AuthorizedClient> T loadAuthorizedClient(
String clientRegistrationId, Authentication principal, HttpServletRequest request){
Assert.hasText(clientRegistrationId, "clientRegistrationId cannot be empty");
Assert.notNull(request, "request cannot be null");
return (OAuth2AuthorizedClient)this
.getAuthorizedClients(request)
.get(clientRegistrationId);
}
这是有道理的,因为没有 HttpServletRequest
可供使用,它在应用程序启动时被调用。
除了让我自己禁止操作外,还有其他解决方法吗OAuth2AuthorizedClientRepository
?
//编辑,
这不是一个完全反应式的堆栈。它是一个 Spring Web 堆栈,其中包含正在使用的 WebClient。
我很清楚 ServerOAuth2AuthorizedClientExchangeFilterFunction
适用于完全反应式堆栈并需要 ReactiveClientRegistrationRepository
和 ReactiveOauth2AuthorizedClient
由于这是在构建在顶部的应用程序中所以不可用Servlet 堆栈,不是反应性的。
我最后问了 Spring 安全团队,
https://github.com/spring-projects/spring-security/issues/6683
不幸的是,如果您在 servlet 堆栈上并在后台线程中使用纯 Spring Security 5 API 调用 OAuth2 资源,则没有 OAuth2AuthorizedClientRepository
可用。
实际上有两种选择,
- 实现一个完全无操作的版本,
var oauth2 = new ServletOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrationRepo,
new OAuth2AuthorizedClientRepository() {
@Override
public <T extends OAuth2AuthorizedClient> T loadAuthorizedClient(String s,
Authentication authentication, HttpServletRequest httpServletRequest) {
return null;
}
@Override
public void saveAuthorizedClient(OAuth2AuthorizedClient oAuth2AuthorizedClient,
Authentication authentication, HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse) {
}
@Override
public void removeAuthorizedClient(String s, Authentication authentication,
HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
}
});
- 实施
UnAuthenticatedServerOAuth2AuthorizedClientRepository
的 Servlet 版本。 UnAuthenticatedServerOAuth2AuthorizedClientRepository GitHub Source 比纯空操作具有一些基本功能。
提供有关 GitHub 问题的反馈可能有助于 Spring 安全团队评估接受 PR 和维护 UnAuthenticatedServerOAuth2AuthorizedClientRepository
的 Servlet 版本
我联系了 Spring 安全团队 Spring Security Issue 6683,据悉,ServerOAuth2AuthorizedClientExchangeFilterFunction
的 Servlet 版本将添加到 Spring Security 5.2 中在非 http 线程上的使用。
因为我也偶然发现了这个问题,所以我将详细说明 Darren Forsythe's 更新的答案,以便其他人更容易找到:
OP 提交的问题导致 OAuth2AuthorizedClientManager
的实现能够
operating outside of a HttpServletRequest context, e.g. in a scheduled/background thread and/or in the service-tier
所述实现 AuthorizedClientServiceOAuth2AuthorizedClientManager
传递给 ServletOAuth2AuthorizedClientExchangeFilterFunction
以替换默认实现。
在我的示例中,它看起来像这样:
@Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientService clientService)
{
OAuth2AuthorizedClientProvider authorizedClientProvider =
OAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials()
.build();
AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager =
new AuthorizedClientServiceOAuth2AuthorizedClientManager(
clientRegistrationRepository, clientService);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
@Bean
WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager)
{
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2 =
new ServletOAuth2AuthorizedClientExchangeFilterFunction(
authorizedClientManager);
oauth2.setDefaultClientRegistrationId("keycloak");
return WebClient.builder().apply(oauth2.oauth2Configuration()).build();
}
给定以下代码,是否可以在应用程序运行器中调用受保护的 API 客户端凭据?
@Bean
public ApplicationRunner test(
WebClient.Builder builder,
ClientRegistrationRepository clientRegistrationRepo,
OAuth2AuthorizedClientRepository authorizedClient) {
return args -> {
try {
var oauth2 =
new ServletOAuth2AuthorizedClientExchangeFilterFunction(
clientRegistrationRepo,
authorizedClient);
oauth2.setDefaultClientRegistrationId("test");
var response = builder
.apply(oauth2.oauth2Configuration())
.build()
.get()
.uri("test")
.retrieve()
.bodyToMono(String.class)
.block();
log.info("Response - {}", response);
} catch (Exception e) {
log.error("Failed to call test.", e);
}
};
}
代码失败是因为,
java.lang.IllegalArgumentException: request cannot be null
全栈,
java.lang.IllegalArgumentException: request cannot be null
at org.springframework.util.Assert.notNull(Assert.java:198) ~[spring-core-5.1.5.RELEASE.jar:5.1.5.RELEASE]
at org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizedClientRepository.loadAuthorizedClient(HttpSessionOAuth2AuthorizedClientRepository.java:47) ~[spring-security-oauth2-client-5.1.4.RELEASE.jar:5.1.4.RELEASE]
at org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction.populateDefaultOAuth2AuthorizedClient(ServletOAuth2AuthorizedClientExchangeFilterFunction.java:364) ~[spring-security-oauth2-client-5.1.4.RELEASE.jar:5.1.4.RELEASE]
at org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction.lambda$null(ServletOAuth2AuthorizedClientExchangeFilterFunction.java:209) ~[spring-security-oauth2-client-5.1.4.RELEASE.jar:5.1.4.RELEASE]
at org.springframework.web.reactive.function.client.DefaultWebClient$DefaultRequestBodyUriSpec.attributes(DefaultWebClient.java:234) ~[spring-webflux-5.1.5.RELEASE.jar:5.1.5.RELEASE]
at org.springframework.web.reactive.function.client.DefaultWebClient$DefaultRequestBodyUriSpec.attributes(DefaultWebClient.java:153) ~[spring-webflux-5.1.5.RELEASE.jar:5.1.5.RELEASE]
失败的方法看起来像,
public <T extends OAuth2AuthorizedClient> T loadAuthorizedClient(
String clientRegistrationId, Authentication principal, HttpServletRequest request){
Assert.hasText(clientRegistrationId, "clientRegistrationId cannot be empty");
Assert.notNull(request, "request cannot be null");
return (OAuth2AuthorizedClient)this
.getAuthorizedClients(request)
.get(clientRegistrationId);
}
这是有道理的,因为没有 HttpServletRequest
可供使用,它在应用程序启动时被调用。
除了让我自己禁止操作外,还有其他解决方法吗OAuth2AuthorizedClientRepository
?
//编辑,
这不是一个完全反应式的堆栈。它是一个 Spring Web 堆栈,其中包含正在使用的 WebClient。
我很清楚 ServerOAuth2AuthorizedClientExchangeFilterFunction
适用于完全反应式堆栈并需要 ReactiveClientRegistrationRepository
和 ReactiveOauth2AuthorizedClient
由于这是在构建在顶部的应用程序中所以不可用Servlet 堆栈,不是反应性的。
我最后问了 Spring 安全团队,
https://github.com/spring-projects/spring-security/issues/6683
不幸的是,如果您在 servlet 堆栈上并在后台线程中使用纯 Spring Security 5 API 调用 OAuth2 资源,则没有 OAuth2AuthorizedClientRepository
可用。
实际上有两种选择,
- 实现一个完全无操作的版本,
var oauth2 = new ServletOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrationRepo,
new OAuth2AuthorizedClientRepository() {
@Override
public <T extends OAuth2AuthorizedClient> T loadAuthorizedClient(String s,
Authentication authentication, HttpServletRequest httpServletRequest) {
return null;
}
@Override
public void saveAuthorizedClient(OAuth2AuthorizedClient oAuth2AuthorizedClient,
Authentication authentication, HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse) {
}
@Override
public void removeAuthorizedClient(String s, Authentication authentication,
HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
}
});
- 实施
UnAuthenticatedServerOAuth2AuthorizedClientRepository
的 Servlet 版本。 UnAuthenticatedServerOAuth2AuthorizedClientRepository GitHub Source 比纯空操作具有一些基本功能。
提供有关 GitHub 问题的反馈可能有助于 Spring 安全团队评估接受 PR 和维护 UnAuthenticatedServerOAuth2AuthorizedClientRepository
的 Servlet 版本
我联系了 Spring 安全团队 Spring Security Issue 6683,据悉,ServerOAuth2AuthorizedClientExchangeFilterFunction
的 Servlet 版本将添加到 Spring Security 5.2 中在非 http 线程上的使用。
因为我也偶然发现了这个问题,所以我将详细说明 Darren Forsythe's 更新的答案,以便其他人更容易找到:
OP 提交的问题导致 OAuth2AuthorizedClientManager
的实现能够
operating outside of a HttpServletRequest context, e.g. in a scheduled/background thread and/or in the service-tier
所述实现 AuthorizedClientServiceOAuth2AuthorizedClientManager
传递给 ServletOAuth2AuthorizedClientExchangeFilterFunction
以替换默认实现。
在我的示例中,它看起来像这样:
@Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientService clientService)
{
OAuth2AuthorizedClientProvider authorizedClientProvider =
OAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials()
.build();
AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager =
new AuthorizedClientServiceOAuth2AuthorizedClientManager(
clientRegistrationRepository, clientService);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
@Bean
WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager)
{
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2 =
new ServletOAuth2AuthorizedClientExchangeFilterFunction(
authorizedClientManager);
oauth2.setDefaultClientRegistrationId("keycloak");
return WebClient.builder().apply(oauth2.oauth2Configuration()).build();
}