Integrating with OAuth2 using SSO.
Summary
In this guide, we’ll integrate with an external OAuth2 service using Single sign-on. We’ll be using the OAuth2 auto configuration provided by spring boot, and then further customize this to our needs.
Starting point
For this guide we’ll start from an application generated by Across Initializr. We’ll start from the default preset (Blank Across application without any modules) and select the option AcrossWebModule.
Setting up OAuth2
During this guide, we’re going to build upon the configuration provided by Spring Boot. As such, we’ll add the following dependencies:
-
Spring Boot starter security, this will enable us to configure basic security within the applications.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
-
Spring Security OAuth, to perform an OAuth2 based login.
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
</dependency>
Configuring OAuth2 settings
Our OAuth2 configuration is entirely based on Spring’s OAuth auto configuration. If you’re already familiar with that topic, you can simply jump over to the next section.
Next up, we’ll add our OAuth2 configuration to our application properties:
security:
oauth2:
client:
clientId: 2117460915207240
clientSecret: a915914ff942cf92cbee88c95ce479
accessTokenUri: https://graph.facebook.com/oauth/access_token
userAuthorizationUri: https://www.facebook.com/dialog/oauth
tokenName: oauth_token
authenticationScheme: query
clientAuthenticationScheme: form
resource:
userInfoUri: https://graph.facebook.com/me
The above clientId and clientSecret are fake, and can not be used to actually perform authentication. To get valid OAuth2 credentials for facebook, take a look at their developer section. |
When referring to the above parameters, we’ve shortened security.oauth2.client and .resource to s.o.c and s.o.r respectively. .parameters
Parameter | Description |
---|---|
s.o.c.clientId |
The clientId given by the OAuth2 provider |
s.o.c.clientSecret |
The clientSecret given by the OAuth2 provider. |
s.o.c.accessTokenUri |
The uri from which we can retrieve the access token. |
s.o.c.userAuthorizationUri |
The uri to retrieve the authorization code from, which can be used to retrieve the access token. |
s.o.c.tokenName |
|
s.o.c.authenticationScheme |
This means that when we’re performing OAuth2 calls, we’ll be sending the access token as a uri parameter.
If we need to send the token through the header, we can use |
s.o.c.clientAuthenticationScheme |
|
s.o.r.userInfoUri |
The uri from which we can retrieve the user information. |
… |
… |
Now, when we try to navigate to our application, we’ll be redirected to Facebook by default. If the user subsequently logs in, he’ll be redirected to our application.
Integration with AdminWebModule
Instead of securing the entire application, let’s just make the administrative section secured by using OAuth2.
Configuring Http Security
To configure the secured path, we’ll require a WebSecurityConfigurerAdapter
. what is
This allows us to configure the security options for various paths.
@Override
protected void configure(HttpSecurity http) throws Exception{
http.antMatcher( adminWeb.path( "/**" ) )
.authorizeRequests()
.and().authorizeRequests().anyRequest().authenticated()
.and().csrf().csrfTokenRepository( CookieCsrfTokenRepository.withHttpOnlyFalse() )
.and().logout().logoutUrl( adminWeb.path( "/logout" ) )
.logoutSuccessUrl( adminWeb.path( "/" ) )
.permitAll()
.logoutRequestMatcher( new AntPathRequestMatcher( adminWeb.path( "/logout" ) ) );
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry urlRegistry =
http.authorizeRequests();
publisher.publishEvent( new AdminWebUrlRegistry( adminWeb, urlRegistry ) );
}
Additionally we’ll need to add an @Order annotation, to ensure that this WebSecurityConfigurerAdapter
is matched before the WebSecurityConfigurerAdapter
configured for AdminWebModule.
If we wouldn’t, our security configuration would be processed after the security configuration for AdminWebModule.
When a security configuration is applicable for a given url, that security will be used and later configurations will not be checked.
We’re also going to be using single sign-on, so we’ll need to add @EnableOAuth2Sso to the configuration class.
@Order(0)
@EnableOAuth2Sso
@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
...
}
AdminWebModule allows developers to configure which permissions grant access to the secured section. As such, we can simply apply the same settings to our new configuration.
AdminWebModuleSettings is not an exposed bean, so we’ll retrieve the instance from the module itself, through the across context.
@Override
protected void configure(HttpSecurity http) throws Exception{
...
AdminWebModuleSettings adminWebModuleSettings=
acrossContext.getModule( AdminWebModule.NAME )
.getAcrossApplicationContextHolder()
.getBeanFactory()
.getBean( AdminWebModuleSettings.class )
String[] authorities = {};
// if we have AdminWebModuleSettings, use those instead.
if (adminWebModuleSettings != null) {
authorities = adminWebModuleSettings.getAccessPermissions();
}
urlRegistry.anyRequest().hasAnyAuthority( authorities );
}
Configuring security configuration
This however isn’t sufficient to ensure that our OAuth2 with Sso is used when navigating to the secured section. We’ll need to inject our configuration in SpringSecurityModule, otherwise our configuration will have no effect …?
Because of a slight difference between @ComponentScan and @Import, we’ll have to make create an intermediate configuration class to inject into SpringSecurityModule.
@Import(OAuth2AutoConfiguration.class)
@ComponentScan(basePackageClasses = WebSecurityConfiguration.class)
@ModuleConfiguration(SpringSecurityModule.NAME)
public class MProfileConfiguration {
}
Since this class injects a configuration class into a different module, we’ll need to put it in the extensions
package.
We also have to make sure that we don’t create unnecessary beans by using @ComponentScan
.
As such, it’s best to insert the WebSecurityConfiguration
class in a separate package, preferably outside the application package.
Integration with UserModule
OAuth2 allows to immediately retrieve user information after the access token is fetched through a ResourceServerTokenServices.
Through the use of OAuth2, we’ll have a Principal set on the SecurityContext.
The principal itself is extracted through the use of a PrincipalExtractor
.
The corresponding authorities for the principal are retrieved through an AuthoritiesExtractor
.
Configuring the authenticated user
UserInfoTokenServices
provides a default PrincipalExtractor
, which is a FixedPrincipalExtractor
.
An AuthoritiesExtractor is simply an intermediate class that creates a Principal
based on the user information that was retrieved by the UserTokenInfoServices
.
We could very easily integrate this with UserModule, by creating a User object based on the user information we’ve received.
Configuring authorities
UserInfoTokenServices
also uses a default AuthoritiesExtractor
, which is a FixedAuthoriesExtractor
.
An AuthoritiesExtractor is simply an intermediate class that extracts the authorities based on the user information that was retrieved by the UserTokenInfoServices
.
This AuthoritiesExtractor looks for the key authorities within the user authorities and converts the presented authorities.
By simply providing an `AuthoritiesExtractor
bean, this functionality can be overridden.
An AuthoritiesExtractor
receives the map containing the keys and values from the user response and is processed after the PrincipalExtractor has executed.
When using UserModule, we can opt in to provide our own AuthoritiesExtractor which creates roles and permissions based on the incoming authorities.