Spring Security Oauth2 with JWT

Learning OAuth2 security protocol has always been one of my prime objectives. After integrating JWT token with Spring Security, I thought of giving a try with learning OAuth2 with Spring Security, as Spring Security provides implementation for OAuth2.

To know the basics of OAuth2 I followed this. This article along with Aaron Parecki's blog really helped me in getting the concept behind this security protocol.

Let's revise the components involved here:

•Resource Owner
•Client
•Resource Server
•Authorization Server

and the grant types are:

•Authorization Code
•Implicit
•Resource Owner Password Credentials
•Client Credentials

We have implemented both the Resource and Authorization server at the same endpoint for the sake of simplicity, however we can implement them separately too.

And out of the four grant types I have been successful in experimenting with the last two only. I will try to share those experience here.

I have selected JWT as the underlying token generation standard.

The Codebase is available in GITHUB.

Spring with @EnableAuthorizationServer annotation and AuthorizationServerConfigurerAdapter class helps in implementing OAuth2 AuthrizationServer.

And for ResourceServer there is @EnableResourceServer annotation and ResourceServerConfigurerAdapter class.

Accessing every resource requires a mandatory valid JWT Token to be present in the request.
Every request is being validated for an autheticated token, if the token is valid then only the resource can be accessed.
If a token validity ends, then we can refresh the access token with refresh token, that is obtained at the very first time when the access token in requested.

Now if the refresh token validity ends we need to fetch the access token again.

All these token generation and refresh tasks are being handled by Authorization server and validation of the tokens (access and refresh both), while request is made for particular resource is being handled by Resource server.

Let's describe this with an example:

At the very beginning, the access token can be accessed at:

For Resource Owner Password Credentials grant type:


http://localhost:8080/SpringTokenOauth2/oauth/token?grant_type=password&username=BOND&password=BOND007&client_id=clientapp2&client_secret=99999
and in the request header there should be:

Authorization Bearer Y2xpZW50YXBwMjo5OTk5OQ==

Y2xpZW50YXBwMjo5OTk5OQ== is clientapp2:99999

And Client Credentials grant type:


http://localhost:8080/SpringTokenOauth2/oauth/token?grant_type=client_credentials&client_id=clientapp2&client_secret=99999

For Client Credentials grant type nothing required to be present in the request header.

The output in the above case would be:


{ "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0NzMwMTQwOTcsInVzZXJfbmFtZSI6IkJPTkQiLCJzY29wZSI6WyJyZWFkIiwid3JpdGUiXSwibWljIjoib2siLCJhdXRob3JpdGllcyI6WyJST0xFX0NMSUVOVCJdLCJhdWQiOlsicmVzdHNlcnZpY2UiXSwianRpIjoiZWVmOTBkZDctZmY4YS00NzNiLTg0NDItYTcyN2U4OGFlZGRiIiwiY2xpZW50X2lkIjoiY2xpZW50YXBwMiJ9.BNxsjqpdXnlDNeXgZZtOvJxawrwGW4yHcizqxkgL4CQ", "token_type": "bearer", "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0NzU2MDYwMDUsInVzZXJfbmFtZSI6IkJPTkQiLCJzY29wZSI6WyJyZWFkIiwid3JpdGUiXSwibWljIjoib2siLCJhdXRob3JpdGllcyI6WyJST0xFX0NMSUVOVCJdLCJhdWQiOlsicmVzdHNlcnZpY2UiXSwianRpIjoiZGE3OTk2NjMtZTM1Mi00NTFjLTgyYzEtYTBjZTQ5MDA4ZjVlIiwiY2xpZW50X2lkIjoiY2xpZW50YXBwMiIsImF0aSI6ImVlZjkwZGQ3LWZmOGEtNDczYi04NDQyLWE3MjdlODhhZWRkYiJ9.FIw0ICyvR2ZibLylyE1huQGMahkWO_W4l8a-QeOISBw", "expires_in": 91, "scope": "read write", "mic": "ok", "jti": "eef90dd7-ff8a-473b-8442-a727e88aeddb" }

Now with the access token as present above we can access resource as:

http://localhost:8080/SpringTokenOauth2/resources/user

and in the request header we have:


Authorization bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0NzMwMTQwOTcsInVzZXJfbmFtZSI6IkJPTkQiLCJzY29wZSI6WyJyZWFkIiwid3JpdGUiXSwibWljIjoib2siLCJhdXRob3JpdGllcyI6WyJST0xFX0NMSUVOVCJdLCJhdWQiOlsicmVzdHNlcnZpY2UiXSwianRpIjoiZWVmOTBkZDctZmY4YS00NzNiLTg0NDItYTcyN2U4OGFlZGRiIiwiY2xpZW50X2lkIjoiY2xpZW50YXBwMiJ9.BNxsjqpdXnlDNeXgZZtOvJxawrwGW4yHcizqxkgL4CQ i.e. Authorization bearer <>

Now when the access token expires, here we have configured the expiry time as 90 seconds (Can be referred from Authorizationserver config), then we no longer can access resource with this access token, we need to use the refresh token to refresh the access token as:


http://localhost:8080/SpringTokenOauth2/oauth/token?grant_type=refresh_token&refresh_token=<>&client_id=clientapp2&client_secret=99999

This endpoint will generate an output like above just in Access Token field we will be getting a new Access Token, the refresh token will remain same.

Now if the refresh token expires, then we need to make the request at the endpoint /oauth/token just like we did at the very beginning. This is very much like fresh login.

Let's explore the AuthorizationServer configuration in detail:

The AuthorizationServer configuration server extends the adapter AuthorizationServerConfigurerAdapter.
This adapter class with the help of ClientDetailsServiceConfigurer helps in embedding client details .

For example:

				clients
.inMemory()
.withClient("clientapp")
.authorizedGrantTypes("authorization_code")
.authorities("ROLE_CLIENT")
.scopes("read", "write")
.resourceIds(RESOURCE_ID)
.redirectUris("http://anywhere?key=value")
.secret("123456")
.and()
.withClient("clientapp2")
.authorizedGrantTypes("client_credentials", "password","refresh_token") .authorities("ROLE_CLIENT")
.scopes("read","write")
.resourceIds(RESOURCE_ID)
.secret("99999")
.accessTokenValiditySeconds(92)


The Client id, secret, scopes, grant types etc. can all be configured with Fluent API. Here we have configured two clients 1. clientapp and 2. clientapp2

In our example only references of clientapp2 as for this client only we have configured "client_credentials", "password","refresh_token" grant types.

The AuthorizationServerEndpointsConfigurer does all token related configuration along with Authentication Manager (and UserDetailsService if necessary)

The underlying token implementation that we have used here is JWT, that is the reason we have used JwtTokenStore and set it in the DefaultTokenServices. The JwtAccessTokenConverter does all the signing work which is required for any JwtToken.
The same converter conversion needs to be present in the Resource Server endpoint so that proper conversion JWT Token can take place which is required for Token validation while accessing the resource.

Other token implementation can also be provided with InMemoryTokenStore etc.

Along with this we have also used Custom Token enhancer, to augment JWT token with some additional information.
For other security related configuration at the Authorization Server end we can use AuthorizationServerSecurityConfigurer, though we have not used here.

Now at the resource server endpoint there is not too much of configuration except for Token store implementation in ResourceServerSecurityConfigurer, which is required for Token validation while accessing resource.

There is also a class named SecurityConfig which is engaged in setting normal Spring Security configuration.
This is in short about Oauth2 implementation with Spring Security.

Please share your thoughts and feedback.

Comments

Popular posts from this blog

Use of @Configurable annotation.

Spring WS - Part 5

Spring WS - Part 4