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
- The clientId and and client secret
- Which resources the client is allowed access to
- The scope (read, write etc.)
- The grant_types (password, authentication etc. ) A very good article to understand different scopes : http://www.bubblecode.net/en/2016/01/22/understanding-oauth2/#Authorization_grant_types
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