parent
d0ebe47cd5
commit
b9ab4929b7
|
@ -31,6 +31,7 @@ import org.springframework.security.authorization.ReactiveAuthorizationManager;
|
|||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.oauth2.client.InMemoryReactiveOAuth2AuthorizedClientService;
|
||||
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService;
|
||||
import org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeReactiveAuthenticationManager;
|
||||
import org.springframework.security.oauth2.client.authentication.OAuth2LoginReactiveAuthenticationManager;
|
||||
import org.springframework.security.oauth2.client.endpoint.WebClientReactiveAuthorizationCodeTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.oidc.authentication.OidcAuthorizationCodeReactiveAuthenticationManager;
|
||||
|
@ -43,6 +44,7 @@ import org.springframework.security.oauth2.client.web.server.OAuth2Authorization
|
|||
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.server.ServerOAuth2AuthorizationCodeAuthenticationTokenConverter;
|
||||
import org.springframework.security.oauth2.client.web.server.OAuth2AuthorizationCodeGrantWebFilter;
|
||||
import org.springframework.security.oauth2.client.web.server.authentication.OAuth2LoginAuthenticationWebFilter;
|
||||
import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder;
|
||||
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
|
||||
|
@ -575,7 +577,7 @@ public class ServerHttpSecurity {
|
|||
* .oauth2()
|
||||
* .resourceServer()
|
||||
* .jwt()
|
||||
* .jwkSeturi(jwkSetUri);
|
||||
* .jwkSetUri(jwkSetUri);
|
||||
* return http.build();
|
||||
* }
|
||||
* </pre>
|
||||
|
@ -597,6 +599,106 @@ public class ServerHttpSecurity {
|
|||
public class OAuth2Spec {
|
||||
private ResourceServerSpec resourceServer;
|
||||
|
||||
private OAuth2ClientSpec client;
|
||||
|
||||
/**
|
||||
* Configures the OAuth2 client.
|
||||
*
|
||||
* <pre class="code">
|
||||
* @Bean
|
||||
* public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
||||
* http
|
||||
* // ...
|
||||
* .oauth2()
|
||||
* .client()
|
||||
* .clientRegistrationRepository(clientRegistrationRepository)
|
||||
* .authorizedClientRepository(authorizedClientRepository);
|
||||
* return http.build();
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
*
|
||||
* @return the {@link OAuth2ClientSpec} to customize
|
||||
*/
|
||||
public OAuth2ClientSpec client() {
|
||||
if (this.client == null) {
|
||||
this.client = new OAuth2ClientSpec();
|
||||
}
|
||||
return this.client;
|
||||
}
|
||||
|
||||
public class OAuth2ClientSpec {
|
||||
private ReactiveClientRegistrationRepository clientRegistrationRepository;
|
||||
|
||||
private ServerOAuth2AuthorizedClientRepository authorizedClientRepository;
|
||||
|
||||
/**
|
||||
* Configures the {@link ReactiveClientRegistrationRepository}. Default is to look the value up as a Bean.
|
||||
* @param clientRegistrationRepository the repository to use
|
||||
* @return the {@link OAuth2ClientSpec} to customize
|
||||
*/
|
||||
public OAuth2ClientSpec clientRegistrationRepository(ReactiveClientRegistrationRepository clientRegistrationRepository) {
|
||||
this.clientRegistrationRepository = clientRegistrationRepository;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the {@link ReactiveClientRegistrationRepository}. Default is to look the value up as a Bean.
|
||||
* @param authorizedClientRepository the repository to use
|
||||
* @return the {@link OAuth2ClientSpec} to customize
|
||||
*/
|
||||
public OAuth2ClientSpec authorizedClientRepository(ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
|
||||
this.authorizedClientRepository = authorizedClientRepository;
|
||||
return this;
|
||||
}
|
||||
|
||||
protected void configure(ServerHttpSecurity http) {
|
||||
ReactiveClientRegistrationRepository clientRegistrationRepository = getClientRegistrationRepository();
|
||||
ServerOAuth2AuthorizedClientRepository authorizedClientRepository = getAuthorizedClientRepository();
|
||||
ReactiveAuthenticationManager authenticationManager = new OAuth2AuthorizationCodeReactiveAuthenticationManager(new WebClientReactiveAuthorizationCodeTokenResponseClient());
|
||||
OAuth2AuthorizationCodeGrantWebFilter codeGrantWebFilter = new OAuth2AuthorizationCodeGrantWebFilter(authenticationManager,
|
||||
clientRegistrationRepository,
|
||||
authorizedClientRepository);
|
||||
|
||||
OAuth2AuthorizationRequestRedirectWebFilter oauthRedirectFilter = new OAuth2AuthorizationRequestRedirectWebFilter(
|
||||
clientRegistrationRepository);
|
||||
http.addFilterAt(codeGrantWebFilter, SecurityWebFiltersOrder.AUTHENTICATION);
|
||||
http.addFilterAt(oauthRedirectFilter, SecurityWebFiltersOrder.HTTP_BASIC);
|
||||
}
|
||||
|
||||
private ReactiveClientRegistrationRepository getClientRegistrationRepository() {
|
||||
if (this.clientRegistrationRepository != null) {
|
||||
return this.clientRegistrationRepository;
|
||||
}
|
||||
return getBeanOrNull(ReactiveClientRegistrationRepository.class);
|
||||
}
|
||||
|
||||
private ServerOAuth2AuthorizedClientRepository getAuthorizedClientRepository() {
|
||||
if (this.authorizedClientRepository != null) {
|
||||
return this.authorizedClientRepository;
|
||||
}
|
||||
ServerOAuth2AuthorizedClientRepository result = getBeanOrNull(ServerOAuth2AuthorizedClientRepository.class);
|
||||
if (result == null) {
|
||||
ReactiveOAuth2AuthorizedClientService authorizedClientService = getAuthorizedClientService();
|
||||
if (authorizedClientService != null) {
|
||||
result = new AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository(
|
||||
authorizedClientService);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private ReactiveOAuth2AuthorizedClientService getAuthorizedClientService() {
|
||||
ReactiveOAuth2AuthorizedClientService service = getBeanOrNull(ReactiveOAuth2AuthorizedClientService.class);
|
||||
if (service == null) {
|
||||
service = new InMemoryReactiveOAuth2AuthorizedClientService(getClientRegistrationRepository());
|
||||
}
|
||||
return service;
|
||||
}
|
||||
|
||||
private OAuth2ClientSpec() {}
|
||||
}
|
||||
|
||||
public ResourceServerSpec resourceServer() {
|
||||
if (this.resourceServer == null) {
|
||||
this.resourceServer = new ResourceServerSpec();
|
||||
|
@ -693,6 +795,9 @@ public class ServerHttpSecurity {
|
|||
if (this.resourceServer != null) {
|
||||
this.resourceServer.configure(http);
|
||||
}
|
||||
if (this.client != null) {
|
||||
this.client.configure(http);
|
||||
}
|
||||
}
|
||||
|
||||
private OAuth2Spec() {}
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.web.server;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
|
||||
import org.springframework.security.config.test.SpringTestRule;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
||||
import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient;
|
||||
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
|
||||
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
|
||||
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
|
||||
import org.springframework.security.test.context.support.WithMockUser;
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.reactive.config.EnableWebFlux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @since 5.1
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SecurityTestExecutionListeners
|
||||
public class OAuth2ClientSpecTests {
|
||||
@Rule
|
||||
public final SpringTestRule spring = new SpringTestRule();
|
||||
|
||||
private WebTestClient client;
|
||||
|
||||
@Autowired
|
||||
public void setApplicationContext(ApplicationContext context) {
|
||||
this.client = WebTestClient.bindToApplicationContext(context).build();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser
|
||||
public void registeredOAuth2AuthorizedClientWhenAuthenticatedThenRedirects() {
|
||||
this.spring.register(Config.class, AuthorizedClientController.class).autowire();
|
||||
ReactiveClientRegistrationRepository repository = this.spring.getContext()
|
||||
.getBean(ReactiveClientRegistrationRepository.class);
|
||||
ServerOAuth2AuthorizedClientRepository authorizedClientRepository = this.spring.getContext().getBean(ServerOAuth2AuthorizedClientRepository.class);
|
||||
when(repository.findByRegistrationId(any())).thenReturn(Mono.just(TestClientRegistrations.clientRegistration().build()));
|
||||
when(authorizedClientRepository.loadAuthorizedClient(any(), any(), any())).thenReturn(Mono.empty());
|
||||
|
||||
this.client.get().uri("/")
|
||||
.exchange()
|
||||
.expectStatus().is3xxRedirection();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void registeredOAuth2AuthorizedClientWhenAnonymousThenRedirects() {
|
||||
this.spring.register(Config.class, AuthorizedClientController.class).autowire();
|
||||
ReactiveClientRegistrationRepository repository = this.spring.getContext()
|
||||
.getBean(ReactiveClientRegistrationRepository.class);
|
||||
ServerOAuth2AuthorizedClientRepository authorizedClientRepository = this.spring.getContext().getBean(ServerOAuth2AuthorizedClientRepository.class);
|
||||
when(repository.findByRegistrationId(any())).thenReturn(Mono.just(TestClientRegistrations.clientRegistration().build()));
|
||||
when(authorizedClientRepository.loadAuthorizedClient(any(), any(), any())).thenReturn(Mono.empty());
|
||||
|
||||
this.client.get().uri("/")
|
||||
.exchange()
|
||||
.expectStatus().is3xxRedirection();
|
||||
}
|
||||
|
||||
@EnableWebFlux
|
||||
@EnableWebFluxSecurity
|
||||
static class Config {
|
||||
@Bean
|
||||
SecurityWebFilterChain springSecurity(ServerHttpSecurity http) {
|
||||
http
|
||||
.oauth2()
|
||||
.client();
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
ReactiveClientRegistrationRepository clientRegistrationRepository() {
|
||||
return mock(ReactiveClientRegistrationRepository.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
ServerOAuth2AuthorizedClientRepository authorizedClientRepository() {
|
||||
return mock(ServerOAuth2AuthorizedClientRepository.class);
|
||||
}
|
||||
}
|
||||
|
||||
@RestController
|
||||
static class AuthorizedClientController {
|
||||
@GetMapping("/")
|
||||
String home(@RegisteredOAuth2AuthorizedClient("github") OAuth2AuthorizedClient authorizedClient) {
|
||||
return "home";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,167 @@
|
|||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.web.server;
|
||||
|
||||
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||||
import org.springframework.security.authentication.ReactiveAuthenticationManager;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
||||
import org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationToken;
|
||||
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.web.server.WebFilterExchange;
|
||||
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationSuccessHandler;
|
||||
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter;
|
||||
import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler;
|
||||
import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler;
|
||||
import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher;
|
||||
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebFilter;
|
||||
import org.springframework.web.server.WebFilterChain;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* A {@code Filter} for the OAuth 2.0 Authorization Code Grant,
|
||||
* which handles the processing of the OAuth 2.0 Authorization Response.
|
||||
*
|
||||
* <p>
|
||||
* The OAuth 2.0 Authorization Response is processed as follows:
|
||||
*
|
||||
* <ul>
|
||||
* <li>
|
||||
* Assuming the End-User (Resource Owner) has granted access to the Client, the Authorization Server will append the
|
||||
* {@link OAuth2ParameterNames#CODE code} and {@link OAuth2ParameterNames#STATE state} parameters
|
||||
* to the {@link OAuth2ParameterNames#REDIRECT_URI redirect_uri} (provided in the Authorization Request)
|
||||
* and redirect the End-User's user-agent back to this {@code Filter} (the Client).
|
||||
* </li>
|
||||
* <li>
|
||||
* This {@code Filter} will then create an {@link OAuth2AuthorizationCodeAuthenticationToken} with
|
||||
* the {@link OAuth2ParameterNames#CODE code} received and
|
||||
* delegate it to the {@link ReactiveAuthenticationManager} to authenticate.
|
||||
* </li>
|
||||
* <li>
|
||||
* Upon a successful authentication, an {@link OAuth2AuthorizedClient Authorized Client} is created by associating the
|
||||
* {@link OAuth2AuthorizationCodeAuthenticationToken#getClientRegistration() client} to the
|
||||
* {@link OAuth2AuthorizationCodeAuthenticationToken#getAccessToken() access token} and current {@code Principal}
|
||||
* and saving it via the {@link ServerOAuth2AuthorizedClientRepository}.
|
||||
* </li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 5.1
|
||||
* @see OAuth2AuthorizationCodeAuthenticationToken
|
||||
* @see org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeReactiveAuthenticationManager
|
||||
* @see OAuth2AuthorizationRequest
|
||||
* @see OAuth2AuthorizationResponse
|
||||
* @see AuthorizationRequestRepository
|
||||
* @see org.springframework.security.oauth2.client.web.server.OAuth2AuthorizationRequestRedirectWebFilter
|
||||
* @see ReactiveClientRegistrationRepository
|
||||
* @see OAuth2AuthorizedClient
|
||||
* @see ServerOAuth2AuthorizedClientRepository
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1">Section 4.1 Authorization Code Grant</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.2">Section 4.1.2 Authorization Response</a>
|
||||
*/
|
||||
public class OAuth2AuthorizationCodeGrantWebFilter implements WebFilter {
|
||||
private final ReactiveAuthenticationManager authenticationManager;
|
||||
|
||||
private final ServerOAuth2AuthorizedClientRepository authorizedClientRepository;
|
||||
|
||||
private ServerAuthenticationSuccessHandler authenticationSuccessHandler;
|
||||
|
||||
private ServerAuthenticationConverter authenticationConverter;
|
||||
|
||||
private ServerAuthenticationFailureHandler authenticationFailureHandler;
|
||||
|
||||
private ServerWebExchangeMatcher requiresAuthenticationMatcher;
|
||||
|
||||
private AnonymousAuthenticationToken anonymousToken = new AnonymousAuthenticationToken("key", "anonymous",
|
||||
AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
|
||||
|
||||
public OAuth2AuthorizationCodeGrantWebFilter(
|
||||
ReactiveAuthenticationManager authenticationManager,
|
||||
ReactiveClientRegistrationRepository clientRegistrationRepository,
|
||||
ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
|
||||
Assert.notNull(authenticationManager, "authenticationManager cannot be null");
|
||||
Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null");
|
||||
Assert.notNull(authorizedClientRepository, "authorizedClientRepository cannot be null");
|
||||
this.authenticationManager = authenticationManager;
|
||||
this.authorizedClientRepository = authorizedClientRepository;
|
||||
this.requiresAuthenticationMatcher = new PathPatternParserServerWebExchangeMatcher("/authorize/oauth2/code/{registrationId}");
|
||||
this.authenticationConverter = new ServerOAuth2AuthorizationCodeAuthenticationTokenConverter(clientRegistrationRepository);
|
||||
this.authenticationSuccessHandler = new RedirectServerAuthenticationSuccessHandler();
|
||||
this.authenticationFailureHandler = (webFilterExchange, exception) -> Mono.error(exception);
|
||||
}
|
||||
|
||||
public OAuth2AuthorizationCodeGrantWebFilter(
|
||||
ReactiveAuthenticationManager authenticationManager,
|
||||
ServerAuthenticationConverter authenticationConverter,
|
||||
ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
|
||||
Assert.notNull(authenticationManager, "authenticationManager cannot be null");
|
||||
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
|
||||
Assert.notNull(authorizedClientRepository, "authorizedClientRepository cannot be null");
|
||||
this.authenticationManager = authenticationManager;
|
||||
this.authorizedClientRepository = authorizedClientRepository;
|
||||
this.requiresAuthenticationMatcher = new PathPatternParserServerWebExchangeMatcher("/authorize/oauth2/code/{registrationId}");
|
||||
this.authenticationConverter = authenticationConverter;
|
||||
this.authenticationSuccessHandler = new RedirectServerAuthenticationSuccessHandler();
|
||||
this.authenticationFailureHandler = (webFilterExchange, exception) -> Mono.error(exception);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
|
||||
return this.requiresAuthenticationMatcher.matches(exchange)
|
||||
.filter( matchResult -> matchResult.isMatch())
|
||||
.flatMap( matchResult -> this.authenticationConverter.convert(exchange))
|
||||
.switchIfEmpty(chain.filter(exchange).then(Mono.empty()))
|
||||
.flatMap( token -> authenticate(exchange, chain, token));
|
||||
}
|
||||
|
||||
private Mono<Void> authenticate(ServerWebExchange exchange,
|
||||
WebFilterChain chain, Authentication token) {
|
||||
WebFilterExchange webFilterExchange = new WebFilterExchange(exchange, chain);
|
||||
return this.authenticationManager.authenticate(token)
|
||||
.switchIfEmpty(Mono.defer(() -> Mono.error(new IllegalStateException("No provider found for " + token.getClass()))))
|
||||
.flatMap(authentication -> onAuthenticationSuccess(authentication, webFilterExchange))
|
||||
.onErrorResume(AuthenticationException.class, e -> this.authenticationFailureHandler
|
||||
.onAuthenticationFailure(webFilterExchange, e));
|
||||
}
|
||||
|
||||
private Mono<Void> onAuthenticationSuccess(Authentication authentication, WebFilterExchange webFilterExchange) {
|
||||
OAuth2AuthorizationCodeAuthenticationToken authenticationResult = (OAuth2AuthorizationCodeAuthenticationToken) authentication;
|
||||
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(
|
||||
authenticationResult.getClientRegistration(),
|
||||
authenticationResult.getName(),
|
||||
authenticationResult.getAccessToken(),
|
||||
authenticationResult.getRefreshToken());
|
||||
return this.authenticationSuccessHandler
|
||||
.onAuthenticationSuccess(webFilterExchange, authentication)
|
||||
.then(ReactiveSecurityContextHolder.getContext()
|
||||
.map(SecurityContext::getAuthentication)
|
||||
.defaultIfEmpty(this.anonymousToken)
|
||||
.flatMap(principal -> this.authorizedClientRepository.saveAuthorizedClient(authorizedClient, principal, webFilterExchange.getExchange()))
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.web.server;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
|
||||
import org.springframework.mock.web.server.MockServerWebExchange;
|
||||
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||||
import org.springframework.security.authentication.ReactiveAuthenticationManager;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationToken;
|
||||
import org.springframework.security.oauth2.client.authentication.TestOAuth2AuthorizationCodeAuthenticationTokens;
|
||||
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
|
||||
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter;
|
||||
import org.springframework.web.server.handler.DefaultWebFilterChain;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @since 5.1
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class OAuth2AuthorizationCodeGrantWebFilterTests {
|
||||
private OAuth2AuthorizationCodeGrantWebFilter filter;
|
||||
@Mock
|
||||
private ReactiveAuthenticationManager authenticationManager;
|
||||
@Mock
|
||||
private ReactiveClientRegistrationRepository clientRegistrationRepository;
|
||||
@Mock
|
||||
private ServerOAuth2AuthorizedClientRepository authorizedClientRepository;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.filter = new OAuth2AuthorizationCodeGrantWebFilter(
|
||||
this.authenticationManager, this.clientRegistrationRepository,
|
||||
this.authorizedClientRepository);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenAuthenticationManagerNullThenIllegalArgumentException() {
|
||||
this.authenticationManager = null;
|
||||
assertThatCode(() -> new OAuth2AuthorizationCodeGrantWebFilter(
|
||||
this.authenticationManager, this.clientRegistrationRepository,
|
||||
this.authorizedClientRepository))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenClientRegistrationRepositoryNullThenIllegalArgumentException() {
|
||||
this.clientRegistrationRepository = null;
|
||||
assertThatCode(() -> new OAuth2AuthorizationCodeGrantWebFilter(
|
||||
this.authenticationManager, this.clientRegistrationRepository,
|
||||
this.authorizedClientRepository))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenAuthorizedClientRepositoryNullThenIllegalArgumentException() {
|
||||
this.authorizedClientRepository = null;
|
||||
assertThatCode(() -> new OAuth2AuthorizationCodeGrantWebFilter(
|
||||
this.authenticationManager, this.clientRegistrationRepository,
|
||||
this.authorizedClientRepository))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void filterWhenNotMatchThenAuthenticationManagerNotCalled() {
|
||||
MockServerWebExchange exchange = MockServerWebExchange
|
||||
.from(MockServerHttpRequest.get("/"));
|
||||
DefaultWebFilterChain chain = new DefaultWebFilterChain(
|
||||
e -> e.getResponse().setComplete());
|
||||
|
||||
this.filter.filter(exchange, chain).block();
|
||||
|
||||
verifyZeroInteractions(this.authenticationManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void filterWhenMatchThenAuthorizedClientSaved() {
|
||||
Mono<Authentication> authentication = Mono
|
||||
.just(TestOAuth2AuthorizationCodeAuthenticationTokens.unauthenticated());
|
||||
OAuth2AuthorizationCodeAuthenticationToken authenticated = TestOAuth2AuthorizationCodeAuthenticationTokens
|
||||
.authenticated();
|
||||
ServerAuthenticationConverter converter = e -> authentication;
|
||||
this.filter = new OAuth2AuthorizationCodeGrantWebFilter(
|
||||
this.authenticationManager, converter, this.authorizedClientRepository);
|
||||
MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest
|
||||
.get("/authorize/oauth2/code/registration-id"));
|
||||
DefaultWebFilterChain chain = new DefaultWebFilterChain(
|
||||
e -> e.getResponse().setComplete());
|
||||
when(this.authenticationManager.authenticate(any())).thenReturn(Mono.just(
|
||||
authenticated));
|
||||
when(this.authorizedClientRepository.saveAuthorizedClient(any(), any(), any()))
|
||||
.thenReturn(Mono.empty());
|
||||
|
||||
this.filter.filter(exchange, chain).block();
|
||||
|
||||
verify(this.authorizedClientRepository).saveAuthorizedClient(any(), any(AnonymousAuthenticationToken.class), any());
|
||||
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue