HttpSecurity.oauth2Login() provides a number of configuration options for setting up OAuth 2.0 authorization. The main configuration options are grouped by their counterparts for protocol endpoints.
For example, oauth2Login().authorizationEndpoint() allows you to configure an authorization endpoint, while oauth2Login().tokenEndpoint() allows you to configure a token endpoint.
The following code shows an example:
@EnableWebSecurity
public class OAuth2LoginSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.oauth2Login(oauth2 -> oauth2
.authorizationEndpoint(authorization -> authorization
...
)
.redirectionEndpoint(redirection -> redirection
...
)
.tokenEndpoint(token -> token
...
)
.userInfoEndpoint(userInfo -> userInfo
...
)
);
return http.build();
}
}
@EnableWebSecurity
class OAuth2LoginSecurityConfig {
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
oauth2Login {
authorizationEndpoint {
...
}
redirectionEndpoint {
...
}
tokenEndpoint {
...
}
userInfoEndpoint {
...
}
}
}
return http.build()
}
}
The main purpose of creating oauth2Login() based on the DSL was to closely match the naming defined in the specifications.
The OAuth 2.0 authorization framework defines protocol endpoints as follows:
The authorization process uses two authorization server endpoints ( HTTP resources):
Authorization endpoint: Used by the client to obtain authorization from the owner of the resource through user agent redirection.
End token point: Used by a client to exchange access permission for an access token, typically when authenticating the client.
Also one client endpoint:
Forwarding endpoint: Used by the authorization server to return responses containing authorization credentials to the client through the resource owner user agent.
The OpenID Connect Core 1.0 specification defines UserInfo endpoint as follows:
The UserInfo endpoint is a protected OAuth resource 2.0, which returns information about the authenticated end user. To obtain the requested end user data, the client makes a request to the UserInfo endpoint using the access token obtained through OpenID Connect authentication. These declared values are typically represented by a JSON object that contains a set of name-value pairs for the declared values.
The following code demonstrates all the configuration options available for oauth2Login() based on DSL:
@EnableWebSecurity
public class OAuth2LoginSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.oauth2Login(oauth2 -> oauth2
.clientRegistrationRepository(this.clientRegistrationRepository())
.authorizedClientRepository(this.authorizedClientRepository())
.authorizedClientService(this.authorizedClientService())
.loginPage("/login")
.authorizationEndpoint(authorization -> authorization
.baseUri(this.authorizationRequestBaseUri())
.authorizationRequestRepository(this.authorizationRequestRepository())
.authorizationRequestResolver(this.authorizationRequestResolver())
)
.redirectionEndpoint(redirection -> redirection
.baseUri(this.authorizationResponseBaseUri())
)
.tokenEndpoint(token -> token
.accessTokenResponseClient(this.accessTokenResponseClient())
)
.userInfoEndpoint(userInfo -> userInfo
.userAuthoritiesMapper(this.userAuthoritiesMapper())
.userService(this.oauth2UserService())
.oidcUserService(this.oidcUserService())
)
);
return http.build();
}
}
@EnableWebSecurity
class OAuth2LoginSecurityConfig {
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
oauth2Login {
clientRegistrationRepository = clientRegistrationRepository()
authorizedClientRepository = authorizedClientRepository()
authorizedClientService = authorizedClientService()
loginPage = "/login"
authorizationEndpoint {
baseUri = authorizationRequestBaseUri()
authorizationRequestRepository = authorizationRequestRepository()
authorizationRequestResolver = authorizationRequestResolver()
}
redirectionEndpoint {
baseUri = authorizationResponseBaseUri()
}
tokenEndpoint {
accessTokenResponseClient = accessTokenResponseClient()
}
userInfoEndpoint {
userAuthoritiesMapper = userAuthoritiesMapper()
userService = oauth2UserService()
oidcUserService = oidcUserService()
}
}
}
return http.build()
}
}
In addition to oauth2Login(), DSL-based configuration also supports XML configuration.
The following code shows all the configuration options available in <security namespace:
<http>
<oauth2-login client-registration-repository-ref="clientRegistrationRepository"
authorized-client-repository-ref="authorizedClientRepository"
authorized-client-service-ref="authorizedClientService"
authorization-request-repository-ref="authorizationRequestRepository"
authorization-request-resolver-ref="authorizationRequestResolver"
access-token-response-client-ref="accessTokenResponseClient"
user-authorities-mapper-ref="userAuthoritiesMapper"
user-service-ref="oauth2UserService"
oidc-user-service-ref="oidcUserService"
login-processing-url="/login/oauth2/code/*"
login-page="/login"
authentication-success-handler-ref="authenticationSuccessHandler"
authentication-failure-handler-ref="authenticationFailureHandler"
jwt-decoder-factory-ref="jwtDecoderFactory"/>
</http>
The following sections describe each of the available configuration options in more detail:
Page OAuth 2.0 login
Redirect endpoint
UserInfo endpoint
Verifying the ID Token Signature
OpenID Connect 1.0 Logout
OAuth 2.0 Login Page
By default, the login page in OAuth 2.0 is automatically generated via DefaultLoginPageGeneratingFilter. The default authorization page displays each configured OAuth client with its ClientRegistration.clientName as a link that can be used to initiate an authorization request (or OAuth 2.0 login).
DefaultLoginPageGeneratingFilter to display links for configured OAuth clients, the registered
ClientRegistrationRepository must also implement
Iterable<ClientRegistration>. See
InMemoryClientRegistrationRepository for reference.
The default link assignment for each OAuth client is as follows:
OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI + "/{registrationId}"
The following line is an example:
<a href="/oauth2/authorization/google">Google</a>
To override the default login page, configure oauth2Login().loginPage() and (optional) oauth2Login().authorizationEndpoint().baseUri().
The following listing is an example:
@EnableWebSecurity
public class OAuth2LoginSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.oauth2Login(oauth2 -> oauth2
.loginPage("/login/oauth2")
...
.authorizationEndpoint(authorization -> authorization
.baseUri("/login/oauth2/authorization")
...
)
);
return http.build();
}
}
@EnableWebSecurity
class OAuth2LoginSecurityConfig {
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
oauth2Login {
loginPage = "/login/oauth2"
authorizationEndpoint {
baseUri = "/login/oauth2/authorization"
}
}
}
return http.build()
}
}
<http>
<oauth2-login login-page="/login/oauth2"
...
/>
</http>
@Controller for the annotation parameter
@RequestMapping("/login/oauth2"), which can render a custom login page.
As noted earlier, adding oauth2Login().authorizationEndpoint().baseUri() to the configuration is optional. However, if you choose to customize your settings, make sure that each OAuth client reference matches authorizationEndpoint().baseUri().
The following line is an example:
<a href="/login/oauth2/ authorization/google">Google</a>
Redirect endpoint
The redirect endpoint is used by the authorization server to return an authorization response (which contains the authorization credentials) to the client through the resource owner's user agent.
The default response to the authorization request for the baseUri (forward endpoint) is /login/oauth2/code/*, which is defined in OAuth2LoginAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI.
If you need to configure the BaseUri authorization response endpoint, configure it , as shown in the following example:
@EnableWebSecurity
public class OAuth2LoginSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.oauth2Login(oauth2 -> oauth2
.redirectionEndpoint(redirection -> redirection
.baseUri("/login/oauth2/callback/*")
...
)
);
return http.build();
}
}
@EnableWebSecurity
class OAuth2LoginSecurityConfig {
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
oauth2Login {
redirectionEndpoint {
baseUri = "/login/oauth2/callback/*"
}
}
}
return http.build()
}
}
<http>
<oauth2-login login-processing-url="/login/oauth2/callback/*"
...
/>
</http>
You also need to make sure that ClientRegistration.redirectUri corresponds to a custom authorization request response for baseUri.
The following listing is an example:
return CommonOAuth2Provider.GOOGLE.getBuilder("google")
.clientId("google-client-id")
.clientSecret("google-client-secret")
.redirectUri("{baseUrl}/login/oauth2/callback/{registrationId}")
.build();
return CommonOAuth2Provider.GOOGLE.getBuilder("google")
.clientId("google-client-id")
.clientSecret("google-client-secret")
.redirectUri("{baseUrl}/login/oauth2/callback/{registrationId}")
.build()
UserInfo endpoint
UserInfo endpoint contains a number of configuration options, described in the following subsections:
User Permissions Mapping
OAuth 2.0 UserService
OpenID Connect 1.0 UserService
User Permissions Display
After the user has successfully authenticated through the OAuth 2.0 provider, OAuth2User.getAuthorities() (or OidcUser.getAuthorities()) can be mapped to a new set of GrantedAuthority instances, which will be passed to OAuth2AuthenticationToken when authentication is complete.
OAuth2AuthenticationToken.getAuthorities() is used to authorize requests, for example in
hasRole('USER') or
hasRole('ADMIN').
There are several options for displaying user permissions:
Usage GrantedAuthoritiesMapper
Delegation-based strategy using OAuth2UserService
Using GrantedAuthoritiesMapper
Specify the implementation GrantedAuthoritiesMapper and configure it as shown in the following example:
@EnableWebSecurity
public class OAuth2LoginSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.oauth2Login(oauth2 -> oauth2
.userInfoEndpoint(userInfo -> userInfo
.userAuthoritiesMapper(this.userAuthoritiesMapper())
...
)
);
return http.build();
}
private GrantedAuthoritiesMapper userAuthoritiesMapper() {
return (authorities) -> {
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
authorities.forEach(authority -> {
if (OidcUserAuthority.class.isInstance(authority)) {
OidcUserAuthority oidcUserAuthority = (OidcUserAuthority)authority;
OidcIdToken idToken = oidcUserAuthority.getIdToken();
OidcUserInfo userInfo = oidcUserAuthority.getUserInfo();
// Display declared values found in idToken and/or userInfo
// to one or more GrantedAuthorities and add it to mappedAuthorities
} else if (OAuth2UserAuthority.class.isInstance(authority)) {
OAuth2UserAuthority oauth2UserAuthority = (OAuth2UserAuthority)authority;
Map<String, Object> userAttributes = oauth2UserAuthority.getAttributes();
// Map the attributes found in userAttributes
// to one or more GrantedAuthorities and add it to mappedAuthorities
}
});
return mappedAuthorities;
};
}
}
@EnableWebSecurity
class OAuth2LoginSecurityConfig {
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
oauth2Login {
userInfoEndpoint {
userAuthoritiesMapper = userAuthoritiesMapper()
}
}
}
return http.build()
}
private fun userAuthoritiesMapper(): GrantedAuthoritiesMapper = GrantedAuthoritiesMapper { authorities: Collection<GrantedAuthority> ->
val mappedAuthorities = emptySet<GrantedAuthority>()
authorities.forEach { authority ->
if (authority is OidcUserAuthority) {
val idToken = authority.idToken
val userInfo = authority.userInfo
// Display declared values found in idToken and/or userInfo
// to one or more GrantedAuthorities and add it to mappedAuthorities
} else if (authority is OAuth2UserAuthority) {
val userAttributes = authority.attributes
// Map the attributes found in userAttributes
// to one or more GrantedAuthorities and add it to mappedAuthorities
}
}
mappedAuthorities
}
}
<http>
<oauth2-login user-authorities-mapper-ref="userAuthoritiesMapper"
...
/>
</http>
Alternatively, you can register a @Bean for the GrantedAuthoritiesMapper so that it is automatically applied to the configuration, as shown in the following example:
@EnableWebSecurity
public class OAuth2LoginSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.oauth2Login(withDefaults());
return http.build();
}
@Bean
public GrantedAuthoritiesMapper userAuthoritiesMapper() {
...
}
}
@EnableWebSecurity
class OAuth2LoginSecurityConfig {
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
oauth2Login { }
}
return http.build()
}
@Bean
fun userAuthoritiesMapper(): GrantedAuthoritiesMapper {
...
}
}
Delegation-based strategy using OAuth2UserService
This strategy is more advanced than using GrantedAuthoritiesMapper, however, it is also more flexible because it gives access to OAuth2UserRequest and OAuth2User (when using OAuth 2.0 UserService) or OidcUserRequest and OidcUser (when using OpenID Connect 1.0 UserService).
OAuth2UserRequest (and OidcUserRequest) provides access to the associated OAuth2AccessToken, which is very useful in cases where the delegator needs to obtain permission information from a protected resource before it can display custom permissions to the user.
The following example shows how to implement and configure a strategy based on delegation using OpenID Connect 1.0 UserService:
@EnableWebSecurity
public class OAuth2LoginSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.oauth2Login(oauth2 -> oauth2
.userInfoEndpoint(userInfo -> userInfo
.oidcUserService(this.oidcUserService())
...
)
);
return http.build();
}
private OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
final OidcUserService delegate = new OidcUserService();
return (userRequest) -> {
// Delegate the powers of the standard implementation to load the user
OidcUser oidcUser = delegate.loadUser(userRequest);
OAuth2AccessToken accessToken = userRequest.getAccessToken();
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
// TODO
// 1) Retrieve authority information from a protected resource using accessToken
// 2) Map the authority information to one or more GrantedAuthorities and add it to mappedAuthorities
// 3) Create a copy of oidcUser, but use mappedAuthorities
oidcUser = new DefaultOidcUser(mappedAuthorities, oidcUser.getIdToken(), oidcUser.getUserInfo());
return oidcUser;
};
}
}
@EnableWebSecurity
class OAuth2LoginSecurityConfig {
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
oauth2Login {
userInfoEndpoint {
oidcUserService = oidcUserService()
}
}
}
return http.build()
}
@Bean
fun oidcUserService(): OAuth2UserService<OidcUserRequest, OidcUser> {
val delegate = OidcUserService()
return OAuth2UserService { userRequest ->
// Delegate the powers of the standard implementation to load the user
var oidcUser = delegate.loadUser(userRequest)
val accessToken = userRequest.accessToken
val mappedAuthorities = HashSet<GrantedAuthority>()
// TODO
// 1) Retrieve authority information from a protected resource using accessToken
// 2) Map the authority information to one or more GrantedAuthorities and add it to mappedAuthorities
// 3) Create a copy of oidcUser, but use mappedAuthorities
oidcUser = DefaultOidcUser(mappedAuthorities, oidcUser.idToken, oidcUser.userInfo)
oidcUser
}
}
}
<http>
<oauth2-login oidc-user-service-ref="oidcUserService"
...
/>
</http>
OAuth 2.0 UserService
DefaultOAuth2UserService is an implementation of OAuth2UserService, which supports standard OAuth 2.0 providers.
OAuth2UserService retrieves the user attributes of the end user (owner resource) from the UserInfo endpoint (using the access token provided to the client during the authorization flow) and returns
AuthenticatedPrincipal in the form
OAuth2User.
DefaultOAuth2UserService uses RestOperations when requesting custom attributes in the UserInfo endpoint.
If you want to configure UserInfo request pre-processing, you can pass DefaultOAuth2UserService.setRequestEntityConverter() with custom Converter<OAuth2UserRequest, RequestEntity<?>>. The standard implementation of OAuth2UserRequestEntityConverter builds a RequestEntity representation of the UserInfo request, which by default sets OAuth2AccessToken in the Authorization header.
On the other hand, if you need to configure post-processing of the UserInfo response, you will need to pass DefaultOAuth2UserService.setRestOperations() with custom configured RestOperations. RestOperations are configured as follows by default:
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
OAuth2ErrorResponseErrorHandler is a ResponseErrorHandler that can handle OAuth 2.0 error (400 Bad Request). It uses OAuth2ErrorHttpMessageConverter to convert OAuth 2.0 error parameters to OAuth2Error.
Whether you configure DefaultOAuth2UserService or pass own OAuth2UserService implementation, you will need to configure it as shown in the following example:
@EnableWebSecurity
public class OAuth2LoginSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.oauth2Login(oauth2 -> oauth2
.userInfoEndpoint(userInfo -> userInfo
.userService(this.oauth2UserService())
...
)
);
return http.build();
}
private OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService() {
...
}
}
@EnableWebSecurity
class OAuth2LoginSecurityConfig {
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
oauth2Login {
userInfoEndpoint {
userService = oauth2UserService()
// ...
}
}
}
return http.build()
}
private fun oauth2UserService(): OAuth2UserService<OAuth2UserRequest, OAuth2User> {
// ...
}
}
OpenID Connect 1.0 UserService
OidcUserService is an implementation of OAuth2UserService that supports the OpenID Connect 1.0 provider.
OidcUserService uses DefaultOAuth2UserService when requesting custom attributes on an endpoint UserInfo.
If you need to configure pre-processing of the UserInfo request and/or post-processing of the UserInfo response, you will need to pass OidcUserService.setOauth2UserService() with a custom configured DefaultOAuth2UserService.
Whether you configure OidcUserService or pass your own OAuth2UserService implementation for the OpenID Connect 1.0 provider, you will need to configure it as shown in the following example :
@EnableWebSecurity
public class OAuth2LoginSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.oauth2Login(oauth2 -> oauth2
.userInfoEndpoint(userInfo -> userInfo
.oidcUserService(this.oidcUserService())
...
)
);
return http.build();
}
private OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
...
}
}
@EnableWebSecurity
class OAuth2LoginSecurityConfig {
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
oauth2Login {
userInfoEndpoint {
oidcUserService = oidcUserService()
// ...
}
}
}
return http.build()
}
private fun oidcUserService(): OAuth2UserService<OidcUserRequest, OidcUser> {
// ...
}
}
ID token signature verification
OpenID Connect 1.0 authentication provides an ID token, which is a security token containing the end user's claimed authentication values from the authorization server when used by the client.
The ID token is represented as a JSON Web Token (JWT) and MUST be signed with JSON web signatures (JWS).
OidcIdTokenDecoderFactory provides JwtDecoder, used to verify the OidcIdToken signature. The default algorithm is RS256, but it can be different if you assign it during client registration. For such cases, the resolver can be configured to return the expected algorithm for JWS assigned to a specific client.
The algorithm resolver for JWS is a Function that accepts a ClientRegistration and returns the expected JwsAlgorithm for the client, such as SignatureAlgorithm.RS256 or MacAlgorithm.HS256.
The following code demonstrates how configure @Bean for OidcIdTokenDecoderFactory, which will default to MacAlgorithm.HS256 for all ClientRegistration:
@Bean
public JwtDecoderFactory<ClientRegistration> idTokenDecoderFactory() {
OidcIdTokenDecoderFactory
idTokenDecoderFactory = new OidcIdTokenDecoderFactory(); idTokenDecoderFactory.setJwsAlgorithmResolver(clientRegistration -> MacAlgorithm.HS256);
return idTokenDecoderFactory;
}
@Bean
fun idTokenDecoderFactory(): JwtDecoderFactory<ClientRegistration?> {
val idTokenDecoderFactory = OidcIdTokenDecoderFactory()
idTokenDecoderFactory.setJwsAlgorithmResolver { MacAlgorithm.HS256 }
return idTokenDecoderFactory
}
HS256,
HS384 or
HS512, is used as the symmetric key for signature verification
client-secret corresponding to
client-id.
ClientRegistration, the algorithm resolver for JWS can evaluate the passed
ClientRegistration to determine which algorithm to return.
OpenID Connect 1.0 logout
OpenID Connect Session Management 1.0 provides the ability to log the end user logout on the provider side using the client. One of the available strategies is RP-Initiated Logout.
If the OpenID provider supports both session management and discovery, the client can receive URL of the endpoint end_session_endpoint from discovery metadata of the OpenID provider. This can be accomplished by configuring ClientRegistration with issuer-uri, as shown in the following example:
spring:
security:
oauth2:
client:
registration:
okta:
client-id: okta-client-id
client-secret: okta-client-secret
...
provider:
okta:
issuer-uri: https://dev-1234.oktapreview.com
...and the OidcClientInitiatedLogoutSuccessHandler, which implements client-initiated logout, can be configured as follows:
@EnableWebSecurity
public class OAuth2LoginSecurityConfig {
@Autowired
private ClientRegistrationRepository clientRegistrationRepository;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
)
.oauth2Login(withDefaults())
.logout(logout -> logout
.logoutSuccessHandler(oidcLogoutSuccessHandler())
);
return http.build();
}
private LogoutSuccessHandler oidcLogoutSuccessHandler() {
OidcClientInitiatedLogoutSuccessHandler oidcLogoutSuccessHandler =
new OidcClientInitiatedLogoutSuccessHandler(this.clientRegistrationRepository);
// Sets the location to which the end user user agent will be redirected
// after logout on the provider side
oidcLogoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}");
return oidcLogoutSuccessHandler;
}
}
@EnableWebSecurity
class OAuth2LoginSecurityConfig {
@Autowired
private lateinit var clientRegistrationRepository: ClientRegistrationRepository
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeRequests {
authorize(anyRequest, authenticated)
}
oauth2Login { }
logout {
logoutSuccessHandler = oidcLogoutSuccessHandler()
}
}
return http.build()
}
private fun oidcLogoutSuccessHandler(): LogoutSuccessHandler {
val oidcLogoutSuccessHandler = OidcClientInitiatedLogoutSuccessHandler(clientRegistrationRepository)
// Sets the location to which the end user user agent will be redirected
// after logout on the provider side
oidcLogoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}")
return oidcLogoutSuccessHandler
}
}
OidcClientInitiatedLogoutSuccessHandler supports placeholder
{baseUrl}. In case of using the main The app's URL, such as
app.example.org will replace it at request time.
GO TO FULL VERSION