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 header as a value instead.

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.

Configuration class that imports the aformentioned WebSecurityConfigurerAdapter.
@Configuration
@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.