Author: Muhammed Shakir

Spring security and spring data are among the core modules of spring framework. Spring security supports almost all the aspects of enterprise security that is expected on server side. 

Spring security has strong support for oAuth 2.0. As we know, oAuth 2.0 is a token base authentication & authorization mechanism. Here the authorization refers to authorizing the app (angular app / react app / any other browser based app / mobile) to access the protected resources on the server. These resources are provided through well-defined REST API. 

For more context, refer to my previous blog on a typical oAuth 2.0 use case

oAuth 2.0 Participants 

It is extremely important that the following roles are understood with respect to oAuth before we get into the details of how spring helps in setting up oAuth 2.0 

  • Client : This is the application (angular or any other client app) that would request for the auth token
  • Auth-Server : This is the application that is supposed to issue the token

The client here represents the client app or any other app that needs a token with which it will access the resources. Now the point is, whether any app can become a client app? Can any client application request for the token? Or is it that that the client must be registered with the auth-server?

Yes - The client must be registered as a valid client with the auth-server. There are other attributes that can be set for the client like

When the client requests for the token, auth-server validates the client before returning the token back. 
Ideally all this client information is stored in the database. 

AuthorizationServerConfigurerAdapter of Spring Security 

You will ideally extend your authorization configuration from
org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter.
This class has 3 significant configuration functions that you may want to override

  • configure(AuthorizationServerSecurityConfigurer)
  • configure(ClientsDetailServerConfigurer)
  • configure(AuthorizationServerEndPointsConfigurer)

Configuring AuthorizationServerSecurity

The method : configure(AuthorizationServerSecurityConfigurer)

This function is very significant as it helps you to configure the behavior of the authorization server itself. You can configure the oAuthServer object to specify who all we are permitted to access the token, check the token. You can also setup the passwordEncoder among many other things.

oauthServer
    .tokenKeyAccess("permitAll()")
    .checkTokenAccess("isAuthenticated()”);

Configuring ClientsDetailsServer

The method : configure(ClientsDetailServerConfigurer)

This is again very important. This helps the oAuthServer to understand the source of the client to validate against.  As mentioned earlier, the client details (apps that would request for token) are ideally stored in the database. So you can configure the datasource which the oAuthServer will use to check the validity.

clients
      .jdbc(dataSource);

Or just in case you want to use inMemory clients 

clients
  .inMemory()
    .withClient("sampleClientId")
    .authorizedGrantTypes("implicit")
    .scopes("read", "write", "foo", "bar")
    .autoApprove(false)
    .accessTokenValiditySeconds(3600);

Configuring the AuthorizationServerEndpoints 

The method : configure(ClientsDetailServerConfigurer)

This is yet another critical configuration where-in you configure how the token will be generated. The token enhancer is set here. The token store is also set here which can be jdbc token store (storing the token in database) or a JwtTokenStore.

final TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
  tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer()));

  // @formatter:off

  endpoints.tokenStore(tokenStore())
    .tokenEnhancer(tokenEnhancerChain)
    .authenticationManager(authenticationManager);
 
   
  // @formatter:on

The token store for JDBC would be defined as follows

@Bean
public TokenStore tokenStore() {
  return new JdbcTokenStore(dataSource());
}

And tokenStore for JwtToken would be defined as follows

 @Bean
    public TokenStore tokenStore() {

        return new JwtTokenStore(accessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        final JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("123");
        final KeyStoreKeyFactory keyStoreKeyFactory = 
          new KeyStoreKeyFactory(new ClassPathResource("mytest.jks"), 
            "mypass".toCharArray());
        converter.setKeyPair(keyStoreKeyFactory.getKeyPair("mytest"));
        return converter;
    }

Configuring the Resource Server

It must also be noted that the resource server must be configured, extending the ResourceServerConfigurerAdapter class to let the resource sever know about how to validate the token received in the request. 

The configuration of Resource

 @Override
    public void configure(final ResourceServerSecurityConfigurer config) {
        config.tokenServices(tokenServices());
    }

    @Bean
    @Primary
    public DefaultTokenServices tokenServices() {
        final DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore());
        return defaultTokenServices;
    }

    // JDBC token store configuration

    @Bean
    public DataSource dataSource() {
        final DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(env.getProperty("jdbc.driverClassName"));
        dataSource.setUrl(env.getProperty("jdbc.url"));
        dataSource.setUsername(env.getProperty("jdbc.user"));
        dataSource.setPassword(env.getProperty("jdbc.pass"));
        return dataSource;
    }

    @Bean
    public TokenStore tokenStore() {
        return new JdbcTokenStore(dataSource());
    }

Wrapping it up 

All in all you configure the two most critical stuff a) Clients b) the tokenStore with the AuthorizationServerConfigurerAdapter. Few more important points with respect to the two

Clients 

  • Details of clients can be stored in the database or inMemory (inMemory only for testing)
  • The clientId & secret is used to validate the client

TokenStore

  • Can be JDBC based token (only these tokens can be revoked)
  • Can be JWT