diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java b/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java
index d1ab52e23c..6e5b4c8281 100644
--- a/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java
+++ b/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java
@@ -1948,6 +1948,103 @@ public final class HttpSecurity extends
return getOrApply(new OAuth2LoginConfigurer<>());
}
+ /**
+ * Configures authentication support using an OAuth 2.0 and/or OpenID Connect 1.0 Provider.
+ *
+ *
+ *
+ * The "authentication flow" is implemented using the Authorization Code Grant, as specified in the
+ * OAuth 2.0 Authorization Framework
+ * and OpenID Connect Core 1.0
+ * specification.
+ *
+ *
+ *
+ * As a prerequisite to using this feature, you must register a client with a provider.
+ * The client registration information may than be used for configuring
+ * a {@link org.springframework.security.oauth2.client.registration.ClientRegistration} using a
+ * {@link org.springframework.security.oauth2.client.registration.ClientRegistration.Builder}.
+ *
+ *
+ *
+ * {@link org.springframework.security.oauth2.client.registration.ClientRegistration}(s) are composed within a
+ * {@link org.springframework.security.oauth2.client.registration.ClientRegistrationRepository},
+ * which is required and must be registered with the {@link ApplicationContext} or
+ * configured via oauth2Login().clientRegistrationRepository(..)
.
+ *
+ *
+ *
+ * The default configuration provides an auto-generated login page at "/login"
and
+ * redirects to "/login?error"
when an authentication error occurs.
+ * The login page will display each of the clients with a link
+ * that is capable of initiating the "authentication flow".
+ *
+ *
+ *
+ *
+ *
Example Configuration
+ *
+ * The following example shows the minimal configuration required, using Google as the Authentication Provider.
+ *
+ *
+ * @Configuration
+ * public class OAuth2LoginConfig {
+ *
+ * @EnableWebSecurity
+ * public static class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {
+ * @Override
+ * protected void configure(HttpSecurity http) throws Exception {
+ * http
+ * .authorizeRequests(authorizeRequests ->
+ * authorizeRequests
+ * .anyRequest().authenticated()
+ * )
+ * .oauth2Login(withDefaults());
+ * }
+ * }
+ *
+ * @Bean
+ * public ClientRegistrationRepository clientRegistrationRepository() {
+ * return new InMemoryClientRegistrationRepository(this.googleClientRegistration());
+ * }
+ *
+ * private ClientRegistration googleClientRegistration() {
+ * return ClientRegistration.withRegistrationId("google")
+ * .clientId("google-client-id")
+ * .clientSecret("google-client-secret")
+ * .clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
+ * .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
+ * .redirectUriTemplate("{baseUrl}/login/oauth2/code/{registrationId}")
+ * .scope("openid", "profile", "email", "address", "phone")
+ * .authorizationUri("https://accounts.google.com/o/oauth2/v2/auth")
+ * .tokenUri("https://www.googleapis.com/oauth2/v4/token")
+ * .userInfoUri("https://www.googleapis.com/oauth2/v3/userinfo")
+ * .userNameAttributeName(IdTokenClaimNames.SUB)
+ * .jwkSetUri("https://www.googleapis.com/oauth2/v3/certs")
+ * .clientName("Google")
+ * .build();
+ * }
+ * }
+ *
+ *
+ *
+ * For more advanced configuration, see {@link OAuth2LoginConfigurer} for available options to customize the defaults.
+ *
+ * @see Section 4.1 Authorization Code Grant
+ * @see Section 3.1 Authorization Code Flow
+ * @see org.springframework.security.oauth2.client.registration.ClientRegistration
+ * @see org.springframework.security.oauth2.client.registration.ClientRegistrationRepository
+ *
+ * @param oauth2LoginCustomizer the {@link Customizer} to provide more options for
+ * the {@link OAuth2LoginConfigurer}
+ * @return the {@link HttpSecurity} for further customizations
+ * @throws Exception
+ */
+ public HttpSecurity oauth2Login(Customizer> oauth2LoginCustomizer) throws Exception {
+ oauth2LoginCustomizer.customize(getOrApply(new OAuth2LoginConfigurer<>()));
+ return HttpSecurity.this;
+ }
+
/**
* Configures OAuth 2.0 Client support.
*
diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java
index 5e0bb87a2f..b54b522591 100644
--- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java
+++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java
@@ -20,6 +20,7 @@ import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.core.ResolvableType;
import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer;
@@ -201,6 +202,20 @@ public final class OAuth2LoginConfigurer> exten
return this.authorizationEndpointConfig;
}
+ /**
+ * Configures the Authorization Server's Authorization Endpoint.
+ *
+ * @param authorizationEndpointCustomizer the {@link Customizer} to provide more options for
+ * the {@link AuthorizationEndpointConfig}
+ * @return the {@link OAuth2LoginConfigurer} for further customizations
+ * @throws Exception
+ */
+ public OAuth2LoginConfigurer authorizationEndpoint(Customizer authorizationEndpointCustomizer)
+ throws Exception {
+ authorizationEndpointCustomizer.customize(this.authorizationEndpointConfig);
+ return this;
+ }
+
/**
* Configuration options for the Authorization Server's Authorization Endpoint.
*/
@@ -268,6 +283,20 @@ public final class OAuth2LoginConfigurer> exten
return this.tokenEndpointConfig;
}
+ /**
+ * Configures the Authorization Server's Token Endpoint.
+ *
+ * @param tokenEndpointCustomizer the {@link Customizer} to provide more options for
+ * the {@link TokenEndpointConfig}
+ * @return the {@link OAuth2LoginConfigurer} for further customizations
+ * @throws Exception
+ */
+ public OAuth2LoginConfigurer tokenEndpoint(Customizer tokenEndpointCustomizer)
+ throws Exception {
+ tokenEndpointCustomizer.customize(this.tokenEndpointConfig);
+ return this;
+ }
+
/**
* Configuration options for the Authorization Server's Token Endpoint.
*/
@@ -310,6 +339,20 @@ public final class OAuth2LoginConfigurer> exten
return this.redirectionEndpointConfig;
}
+ /**
+ * Configures the Client's Redirection Endpoint.
+ *
+ * @param redirectionEndpointCustomizer the {@link Customizer} to provide more options for
+ * the {@link RedirectionEndpointConfig}
+ * @return the {@link OAuth2LoginConfigurer} for further customizations
+ * @throws Exception
+ */
+ public OAuth2LoginConfigurer redirectionEndpoint(Customizer redirectionEndpointCustomizer)
+ throws Exception {
+ redirectionEndpointCustomizer.customize(this.redirectionEndpointConfig);
+ return this;
+ }
+
/**
* Configuration options for the Client's Redirection Endpoint.
*/
@@ -350,6 +393,20 @@ public final class OAuth2LoginConfigurer> exten
return this.userInfoEndpointConfig;
}
+ /**
+ * Configures the Authorization Server's UserInfo Endpoint.
+ *
+ * @param userInfoEndpointCustomizer the {@link Customizer} to provide more options for
+ * the {@link UserInfoEndpointConfig}
+ * @return the {@link OAuth2LoginConfigurer} for further customizations
+ * @throws Exception
+ */
+ public OAuth2LoginConfigurer userInfoEndpoint(Customizer userInfoEndpointCustomizer)
+ throws Exception {
+ userInfoEndpointCustomizer.customize(this.userInfoEndpointConfig);
+ return this;
+ }
+
/**
* Configuration options for the Authorization Server's UserInfo Endpoint.
*/
diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurerTests.java
index f33c9d0201..a61a99f872 100644
--- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurerTests.java
+++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurerTests.java
@@ -176,6 +176,25 @@ public class OAuth2LoginConfigurerTests {
.isInstanceOf(OAuth2UserAuthority.class).hasToString("ROLE_USER");
}
+ @Test
+ public void requestWhenOauth2LoginInLambdaThenAuthenticationContainsOauth2UserAuthority() throws Exception {
+ loadConfig(OAuth2LoginInLambdaConfig.class);
+ OAuth2AuthorizationRequest authorizationRequest = createOAuth2AuthorizationRequest();
+ this.authorizationRequestRepository.saveAuthorizationRequest(
+ authorizationRequest, this.request, this.response);
+ this.request.setParameter("code", "code123");
+ this.request.setParameter("state", authorizationRequest.getState());
+
+ this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain);
+
+ Authentication authentication = this.securityContextRepository
+ .loadContext(new HttpRequestResponseHolder(this.request, this.response))
+ .getAuthentication();
+ assertThat(authentication.getAuthorities()).hasSize(1);
+ assertThat(authentication.getAuthorities()).first()
+ .isInstanceOf(OAuth2UserAuthority.class).hasToString("ROLE_USER");
+ }
+
// gh-6009
@Test
public void oauth2LoginWhenSuccessThenAuthenticationSuccessEventPublished() throws Exception {
@@ -303,6 +322,29 @@ public class OAuth2LoginConfigurerTests {
assertThat(this.response.getRedirectedUrl()).isEqualTo("https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=clientId&scope=openid+profile+email&state=state&redirect_uri=http%3A%2F%2Flocalhost%2Flogin%2Foauth2%2Fcode%2Fgoogle&custom-param1=custom-value1");
}
+ @Test
+ public void requestWhenOauth2LoginWithCustomAuthorizationRequestParametersThenParametersInRedirectedUrl()
+ throws Exception {
+ loadConfig(OAuth2LoginConfigCustomAuthorizationRequestResolverInLambda.class);
+ OAuth2AuthorizationRequestResolver resolver = this.context.getBean(
+ OAuth2LoginConfigCustomAuthorizationRequestResolverInLambda.class).resolver;
+ OAuth2AuthorizationRequest result = OAuth2AuthorizationRequest.authorizationCode()
+ .authorizationUri("https://accounts.google.com/authorize")
+ .clientId("client-id")
+ .state("adsfa")
+ .authorizationRequestUri("https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=clientId&scope=openid+profile+email&state=state&redirect_uri=http%3A%2F%2Flocalhost%2Flogin%2Foauth2%2Fcode%2Fgoogle&custom-param1=custom-value1")
+ .build();
+ when(resolver.resolve(any())).thenReturn(result);
+
+ String requestUri = "/oauth2/authorization/google";
+ this.request = new MockHttpServletRequest("GET", requestUri);
+ this.request.setServletPath(requestUri);
+
+ this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain);
+
+ assertThat(this.response.getRedirectedUrl()).isEqualTo("https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=clientId&scope=openid+profile+email&state=state&redirect_uri=http%3A%2F%2Flocalhost%2Flogin%2Foauth2%2Fcode%2Fgoogle&custom-param1=custom-value1");
+ }
+
// gh-5347
@Test
public void oauth2LoginWithOneClientConfiguredThenRedirectForAuthorization() throws Exception {
@@ -374,6 +416,19 @@ public class OAuth2LoginConfigurerTests {
assertThat(this.response.getRedirectedUrl()).matches("http://localhost/custom-login");
}
+ @Test
+ public void requestWhenOauth2LoginWithCustomLoginPageInLambdaThenRedirectCustomLoginPage() throws Exception {
+ loadConfig(OAuth2LoginConfigCustomLoginPageInLambda.class);
+
+ String requestUri = "/";
+ this.request = new MockHttpServletRequest("GET", requestUri);
+ this.request.setServletPath(requestUri);
+
+ this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain);
+
+ assertThat(this.response.getRedirectedUrl()).matches("http://localhost/custom-login");
+ }
+
@Test
public void oidcLogin() throws Exception {
// setup application context
@@ -400,6 +455,32 @@ public class OAuth2LoginConfigurerTests {
.isInstanceOf(OidcUserAuthority.class).hasToString("ROLE_USER");
}
+ @Test
+ public void requestWhenOauth2LoginInLambdaAndOidcThenAuthenticationContainsOidcUserAuthority() throws Exception {
+ // setup application context
+ loadConfig(OAuth2LoginInLambdaConfig.class, JwtDecoderFactoryConfig.class);
+
+ // setup authorization request
+ OAuth2AuthorizationRequest authorizationRequest = createOAuth2AuthorizationRequest("openid");
+ this.authorizationRequestRepository.saveAuthorizationRequest(
+ authorizationRequest, this.request, this.response);
+
+ // setup authentication parameters
+ this.request.setParameter("code", "code123");
+ this.request.setParameter("state", authorizationRequest.getState());
+
+ // perform test
+ this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain);
+
+ // assertions
+ Authentication authentication = this.securityContextRepository
+ .loadContext(new HttpRequestResponseHolder(this.request, this.response))
+ .getAuthentication();
+ assertThat(authentication.getAuthorities()).hasSize(1);
+ assertThat(authentication.getAuthorities()).first()
+ .isInstanceOf(OidcUserAuthority.class).hasToString("ROLE_USER");
+ }
+
@Test
public void oidcLoginCustomWithConfigurer() throws Exception {
// setup application context
@@ -521,6 +602,30 @@ public class OAuth2LoginConfigurerTests {
}
}
+ @EnableWebSecurity
+ static class OAuth2LoginInLambdaConfig extends CommonLambdaWebSecurityConfigurerAdapter
+ implements ApplicationListener {
+ static List EVENTS = new ArrayList<>();
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ // @formatter:off
+ http
+ .oauth2Login(oauth2Login ->
+ oauth2Login
+ .clientRegistrationRepository(
+ new InMemoryClientRegistrationRepository(GOOGLE_CLIENT_REGISTRATION))
+ );
+ // @formatter:on
+ super.configure(http);
+ }
+
+ @Override
+ public void onApplicationEvent(AuthenticationSuccessEvent event) {
+ EVENTS.add(event);
+ }
+ }
+
@EnableWebSecurity
static class OAuth2LoginConfigCustomWithConfigurer extends CommonWebSecurityConfigurerAdapter {
@Override
@@ -586,6 +691,28 @@ public class OAuth2LoginConfigurerTests {
}
}
+ @EnableWebSecurity
+ static class OAuth2LoginConfigCustomAuthorizationRequestResolverInLambda extends CommonLambdaWebSecurityConfigurerAdapter {
+ private ClientRegistrationRepository clientRegistrationRepository =
+ new InMemoryClientRegistrationRepository(GOOGLE_CLIENT_REGISTRATION);
+
+ OAuth2AuthorizationRequestResolver resolver = mock(OAuth2AuthorizationRequestResolver.class);
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ http
+ .oauth2Login(oauth2Login ->
+ oauth2Login
+ .clientRegistrationRepository(this.clientRegistrationRepository)
+ .authorizationEndpoint(authorizationEndpoint ->
+ authorizationEndpoint
+ .authorizationRequestResolver(this.resolver)
+ )
+ );
+ super.configure(http);
+ }
+ }
+
@EnableWebSecurity
static class OAuth2LoginConfigMultipleClients extends CommonWebSecurityConfigurerAdapter {
@Override
@@ -612,6 +739,23 @@ public class OAuth2LoginConfigurerTests {
}
}
+ @EnableWebSecurity
+ static class OAuth2LoginConfigCustomLoginPageInLambda extends CommonLambdaWebSecurityConfigurerAdapter {
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ // @formatter:off
+ http
+ .oauth2Login(oauth2Login ->
+ oauth2Login
+ .clientRegistrationRepository(
+ new InMemoryClientRegistrationRepository(GOOGLE_CLIENT_REGISTRATION))
+ .loginPage("/custom-login")
+ );
+ // @formatter:on
+ super.configure(http);
+ }
+ }
+
@EnableWebSecurity
static class OAuth2LoginConfigWithOidcLogoutSuccessHandler extends CommonWebSecurityConfigurerAdapter {
@Override
@@ -667,6 +811,45 @@ public class OAuth2LoginConfigurerTests {
}
}
+ private static abstract class CommonLambdaWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ // @formatter:off
+ http
+ .authorizeRequests(authorizeRequests ->
+ authorizeRequests
+ .anyRequest().authenticated()
+ )
+ .securityContext(securityContext ->
+ securityContext
+ .securityContextRepository(securityContextRepository())
+ )
+ .oauth2Login(oauth2Login ->
+ oauth2Login
+ .tokenEndpoint(tokenEndpoint ->
+ tokenEndpoint
+ .accessTokenResponseClient(createOauth2AccessTokenResponseClient())
+ )
+ .userInfoEndpoint(userInfoEndpoint ->
+ userInfoEndpoint
+ .userService(createOauth2UserService())
+ .oidcUserService(createOidcUserService())
+ )
+ );
+ // @formatter:on
+ }
+
+ @Bean
+ SecurityContextRepository securityContextRepository() {
+ return new HttpSessionSecurityContextRepository();
+ }
+
+ @Bean
+ HttpSessionOAuth2AuthorizationRequestRepository oauth2AuthorizationRequestRepository() {
+ return new HttpSessionOAuth2AuthorizationRequestRepository();
+ }
+ }
+
@Configuration
static class JwtDecoderFactoryConfig {