Add OAuth2AuthorizationCodeGrantWebFilter

Issue: gh-5620
This commit is contained in:
Rob Winch
2018-08-02 15:35:20 -05:00
parent d0ebe47cd5
commit b9ab4929b7
4 changed files with 519 additions and 1 deletions

View File

@@ -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">
* &#064;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() {}

View File

@@ -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";
}
}
}