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