diff --git a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java index 78d5921163..97690e577c 100644 --- a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java +++ b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java @@ -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(); * } * @@ -597,6 +599,106 @@ public class ServerHttpSecurity { public class OAuth2Spec { private ResourceServerSpec resourceServer; + private OAuth2ClientSpec client; + + /** + * Configures the OAuth2 client. + * + *
+ * @Bean
+ * public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
+ * http
+ * // ...
+ * .oauth2()
+ * .client()
+ * .clientRegistrationRepository(clientRegistrationRepository)
+ * .authorizedClientRepository(authorizedClientRepository);
+ * return http.build();
+ * }
+ *
+ *
+ *
+ * @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() {}
diff --git a/config/src/test/java/org/springframework/security/config/web/server/OAuth2ClientSpecTests.java b/config/src/test/java/org/springframework/security/config/web/server/OAuth2ClientSpecTests.java
new file mode 100644
index 0000000000..cae98a8934
--- /dev/null
+++ b/config/src/test/java/org/springframework/security/config/web/server/OAuth2ClientSpecTests.java
@@ -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";
+ }
+ }
+}
diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/OAuth2AuthorizationCodeGrantWebFilter.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/OAuth2AuthorizationCodeGrantWebFilter.java
new file mode 100644
index 0000000000..25ab879773
--- /dev/null
+++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/OAuth2AuthorizationCodeGrantWebFilter.java
@@ -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.
+ *
+ * + * The OAuth 2.0 Authorization Response is processed as follows: + * + *