OAuth2AuthorizedClientArgumentResolver Uses ServerOAuth2AuthorizedClientRepository

Issue: gh-5621
This commit is contained in:
Rob Winch 2018-08-09 21:25:16 -05:00
parent 1d57a084aa
commit dd7925cb63
3 changed files with 44 additions and 26 deletions

View File

@ -21,6 +21,8 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportSelector; import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.AnnotationMetadata;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService; import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.web.server.AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.client.web.reactive.result.method.annotation.OAuth2AuthorizedClientArgumentResolver; import org.springframework.security.oauth2.client.web.reactive.result.method.annotation.OAuth2AuthorizedClientArgumentResolver;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import org.springframework.web.reactive.config.WebFluxConfigurer; import org.springframework.web.reactive.config.WebFluxConfigurer;
@ -51,20 +53,37 @@ final class ReactiveOAuth2ClientImportSelector implements ImportSelector {
@Configuration @Configuration
static class OAuth2ClientWebFluxSecurityConfiguration implements WebFluxConfigurer { static class OAuth2ClientWebFluxSecurityConfiguration implements WebFluxConfigurer {
private ServerOAuth2AuthorizedClientRepository authorizedClientRepository;
private ReactiveOAuth2AuthorizedClientService authorizedClientService; private ReactiveOAuth2AuthorizedClientService authorizedClientService;
@Override @Override
public void configureArgumentResolvers(ArgumentResolverConfigurer configurer) { public void configureArgumentResolvers(ArgumentResolverConfigurer configurer) {
if (this.authorizedClientService != null) { if (this.authorizedClientRepository != null) {
configurer.addCustomResolver(new OAuth2AuthorizedClientArgumentResolver(this.authorizedClientService)); configurer.addCustomResolver(new OAuth2AuthorizedClientArgumentResolver(getAuthorizedClientRepository()));
} }
} }
@Autowired(required = false)
public void setAuthorizedClientRepository(ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
this.authorizedClientRepository = authorizedClientRepository;
}
@Autowired(required = false) @Autowired(required = false)
public void setAuthorizedClientService(List<ReactiveOAuth2AuthorizedClientService> authorizedClientService) { public void setAuthorizedClientService(List<ReactiveOAuth2AuthorizedClientService> authorizedClientService) {
if (authorizedClientService.size() == 1) { if (authorizedClientService.size() == 1) {
this.authorizedClientService = authorizedClientService.get(0); this.authorizedClientService = authorizedClientService.get(0);
} }
} }
private ServerOAuth2AuthorizedClientRepository getAuthorizedClientRepository() {
if (this.authorizedClientRepository != null) {
return this.authorizedClientRepository;
}
if (this.authorizedClientService != null) {
return new AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository(this.authorizedClientService);
}
return null;
}
} }
} }

View File

@ -18,14 +18,16 @@ package org.springframework.security.oauth2.client.web.reactive.result.method.an
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.ReactiveSecurityContextHolder; import org.springframework.security.core.context.ReactiveSecurityContextHolder;
import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.oauth2.client.ClientAuthorizationRequiredException; import org.springframework.security.oauth2.client.ClientAuthorizationRequiredException;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient; import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.reactive.BindingContext; import org.springframework.web.reactive.BindingContext;
@ -54,16 +56,16 @@ import reactor.core.publisher.Mono;
* @see RegisteredOAuth2AuthorizedClient * @see RegisteredOAuth2AuthorizedClient
*/ */
public final class OAuth2AuthorizedClientArgumentResolver implements HandlerMethodArgumentResolver { public final class OAuth2AuthorizedClientArgumentResolver implements HandlerMethodArgumentResolver {
private final ReactiveOAuth2AuthorizedClientService authorizedClientService; private final ServerOAuth2AuthorizedClientRepository authorizedClientRepository;
/** /**
* Constructs an {@code OAuth2AuthorizedClientArgumentResolver} using the provided parameters. * Constructs an {@code OAuth2AuthorizedClientArgumentResolver} using the provided parameters.
* *
* @param authorizedClientService the authorized client service * @param authorizedClientRepository the authorized client repository
*/ */
public OAuth2AuthorizedClientArgumentResolver(ReactiveOAuth2AuthorizedClientService authorizedClientService) { public OAuth2AuthorizedClientArgumentResolver(ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
Assert.notNull(authorizedClientService, "authorizedClientService cannot be null"); Assert.notNull(authorizedClientRepository, "authorizedClientRepository cannot be null");
this.authorizedClientService = authorizedClientService; this.authorizedClientRepository = authorizedClientRepository;
} }
@Override @Override
@ -84,20 +86,22 @@ public final class OAuth2AuthorizedClientArgumentResolver implements HandlerMeth
.switchIfEmpty(Mono.defer(() -> Mono.error(new IllegalArgumentException( .switchIfEmpty(Mono.defer(() -> Mono.error(new IllegalArgumentException(
"Unable to resolve the Client Registration Identifier. It must be provided via @RegisteredOAuth2AuthorizedClient(\"client1\") or @RegisteredOAuth2AuthorizedClient(registrationId = \"client1\").")))); "Unable to resolve the Client Registration Identifier. It must be provided via @RegisteredOAuth2AuthorizedClient(\"client1\") or @RegisteredOAuth2AuthorizedClient(registrationId = \"client1\")."))));
Mono<String> principalName = ReactiveSecurityContextHolder.getContext() Mono<Authentication> principal = ReactiveSecurityContextHolder.getContext()
.map(SecurityContext::getAuthentication).map(Authentication::getName); .map(SecurityContext::getAuthentication)
.defaultIfEmpty(new AnonymousAuthenticationToken("key", "anonymous",
AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS")));
Mono<OAuth2AuthorizedClient> authorizedClient = Mono Mono<OAuth2AuthorizedClient> authorizedClient = Mono
.zip(clientRegistrationId, principalName).switchIfEmpty( .zip(clientRegistrationId, principal).switchIfEmpty(
clientRegistrationId.flatMap(id -> Mono.error(new IllegalStateException( clientRegistrationId.flatMap(id -> Mono.error(new IllegalStateException(
"Unable to resolve the Authorized Client with registration identifier \"" "Unable to resolve the Authorized Client with registration identifier \""
+ id + id
+ "\". An \"authenticated\" or \"unauthenticated\" session is required. To allow for unauthenticated access, ensure ServerHttpSecurity.anonymous() is configured.")))) + "\". An \"authenticated\" or \"unauthenticated\" session is required. To allow for unauthenticated access, ensure ServerHttpSecurity.anonymous() is configured."))))
.flatMap(zipped -> { .flatMap(zipped -> {
String registrationId = zipped.getT1(); String registrationId = zipped.getT1();
String username = zipped.getT2(); Authentication authentication = zipped.getT2();
return this.authorizedClientService return this.authorizedClientRepository
.loadAuthorizedClient(registrationId, username).switchIfEmpty(Mono.defer(() -> Mono .loadAuthorizedClient(registrationId, authentication, exchange).switchIfEmpty(Mono.defer(() -> Mono
.error(new ClientAuthorizationRequiredException( .error(new ClientAuthorizationRequiredException(
registrationId)))); registrationId))));
}).cast(OAuth2AuthorizedClient.class); }).cast(OAuth2AuthorizedClient.class);

View File

@ -27,9 +27,9 @@ import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.ReactiveSecurityContextHolder; import org.springframework.security.core.context.ReactiveSecurityContextHolder;
import org.springframework.security.oauth2.client.ClientAuthorizationRequiredException; import org.springframework.security.oauth2.client.ClientAuthorizationRequiredException;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient; import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
import reactor.core.publisher.Hooks; import reactor.core.publisher.Hooks;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
@ -51,7 +51,7 @@ import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
public class OAuth2AuthorizedClientArgumentResolverTests { public class OAuth2AuthorizedClientArgumentResolverTests {
@Mock @Mock
private ReactiveOAuth2AuthorizedClientService authorizedClientService; private ServerOAuth2AuthorizedClientRepository authorizedClientRepository;
private OAuth2AuthorizedClientArgumentResolver argumentResolver; private OAuth2AuthorizedClientArgumentResolver argumentResolver;
private OAuth2AuthorizedClient authorizedClient; private OAuth2AuthorizedClient authorizedClient;
@ -59,9 +59,9 @@ public class OAuth2AuthorizedClientArgumentResolverTests {
@Before @Before
public void setUp() { public void setUp() {
this.argumentResolver = new OAuth2AuthorizedClientArgumentResolver(this.authorizedClientService); this.argumentResolver = new OAuth2AuthorizedClientArgumentResolver(this.authorizedClientRepository);
this.authorizedClient = mock(OAuth2AuthorizedClient.class); this.authorizedClient = mock(OAuth2AuthorizedClient.class);
when(this.authorizedClientService.loadAuthorizedClient(anyString(), any())).thenReturn(Mono.just(this.authorizedClient)); when(this.authorizedClientRepository.loadAuthorizedClient(anyString(), any(), any())).thenReturn(Mono.just(this.authorizedClient));
Hooks.onOperatorDebug(); Hooks.onOperatorDebug();
} }
@ -100,21 +100,16 @@ public class OAuth2AuthorizedClientArgumentResolverTests {
@Test @Test
public void resolveArgumentWhenRegistrationIdEmptyAndOAuth2AuthenticationThenResolves() { public void resolveArgumentWhenRegistrationIdEmptyAndOAuth2AuthenticationThenResolves() {
this.authentication = mock(OAuth2AuthenticationToken.class); this.authentication = mock(OAuth2AuthenticationToken.class);
when(this.authentication.getName()).thenReturn("client1");
when(((OAuth2AuthenticationToken) this.authentication).getAuthorizedClientRegistrationId()).thenReturn("client1"); when(((OAuth2AuthenticationToken) this.authentication).getAuthorizedClientRegistrationId()).thenReturn("client1");
MethodParameter methodParameter = this.getMethodParameter("registrationIdEmpty", OAuth2AuthorizedClient.class); MethodParameter methodParameter = this.getMethodParameter("registrationIdEmpty", OAuth2AuthorizedClient.class);
resolveArgument(methodParameter); resolveArgument(methodParameter);
} }
@Test @Test
public void resolveArgumentWhenParameterTypeOAuth2AuthorizedClientAndCurrentAuthenticationNullThenThrowIllegalStateException() { public void resolveArgumentWhenParameterTypeOAuth2AuthorizedClientAndCurrentAuthenticationNullThenResolves() {
this.authentication = null; this.authentication = null;
MethodParameter methodParameter = this.getMethodParameter("paramTypeAuthorizedClient", OAuth2AuthorizedClient.class); MethodParameter methodParameter = this.getMethodParameter("paramTypeAuthorizedClient", OAuth2AuthorizedClient.class);
assertThatThrownBy(() -> resolveArgument(methodParameter)) assertThat(resolveArgument(methodParameter)).isSameAs(this.authorizedClient);
.isInstanceOf(IllegalStateException.class)
.hasMessage("Unable to resolve the Authorized Client with registration identifier \"client1\". " +
"An \"authenticated\" or \"unauthenticated\" session is required. " +
"To allow for unauthenticated access, ensure ServerHttpSecurity.anonymous() is configured.");
} }
@Test @Test
@ -125,7 +120,7 @@ public class OAuth2AuthorizedClientArgumentResolverTests {
@Test @Test
public void resolveArgumentWhenOAuth2AuthorizedClientNotFoundThenThrowClientAuthorizationRequiredException() { public void resolveArgumentWhenOAuth2AuthorizedClientNotFoundThenThrowClientAuthorizationRequiredException() {
when(this.authorizedClientService.loadAuthorizedClient(anyString(), any())).thenReturn(Mono.empty()); when(this.authorizedClientRepository.loadAuthorizedClient(anyString(), any(), any())).thenReturn(Mono.empty());
MethodParameter methodParameter = this.getMethodParameter("paramTypeAuthorizedClient", OAuth2AuthorizedClient.class); MethodParameter methodParameter = this.getMethodParameter("paramTypeAuthorizedClient", OAuth2AuthorizedClient.class);
assertThatThrownBy(() -> resolveArgument(methodParameter)) assertThatThrownBy(() -> resolveArgument(methodParameter))
.isInstanceOf(ClientAuthorizationRequiredException.class); .isInstanceOf(ClientAuthorizationRequiredException.class);