I started to use Spring OAuth2 and in the process, I struggled to find relevant tutorials and content mostly because of the following
I started off with basic setup of Spring Security framework using mainly XML configuration.
Spring Security OAuth2 I did a ton of google searches and looked at a bunch of repositories including the main Spring OAuth 2 repo.
I started off with OAuth2 XML Configs and I needed to make changes in order to
/oauth/check_token
and validate JWT token custom claimsAnd, I have achieved the above as follow
The default spring-security.xml linked above comes with the following assumptions
I would like to keep the Authentication and Resource server separate hence, I stripped off all the protected endpoints configs and the in-memory user-service and oauth-client-details.
To achieve this, I made sure that
org.springframework.security.core.userdetails.UserDetails
and overrides the getAuthorities which return a collection of GrantedAuthorityorg.springframework.security.core.GrantedAuthority
and override the method getAuthority I now need to inform the Spring OAuth2 about the above customised MyUser and MyRole and in order to do that, I needed to do the following
org.springframework.security.core.userdetails.UserDetailsService
interface and override the inherited loadByUsername method to query my custom user database table and retrieve the user with its roles and construct an instance of org.springframework.security.core.userdetails.User
and return it. Create a
of the above custom implementation and let's call it MyUserDetailsService
and place it in spring-security.xml config file. Something as follow
Just defining a bean is once again not enough, we have to tell Spring OAuth2 to use the MyUserDetailsService
and to do that we need to inject the myUserDetailsService
into Spring OAuth2's default authentication provider or our own authentication provider that will then get passed to authentication-manager element, as shown below
Finally we need to inject the above Authentication Provider into the Authentication Manager specified in spring-security.xml as shown below
Change the above with below
And now to use the above Authentication Manager which uses, pass it to a grant flow in the
as follow
If you look closely at the default spring-security.xml from the official Spring OAuth2 repo linked above, you will see the following chain of references
clientCredentialsTokenEndpointFilter
bean
references
clientAuthenticationManager
bean
references
clientDetailsUserDetailsService
references
clientDetails
And clientDetails
is nothing but a list of fixed oauth clients declared at the end under
tag.
Alright, so the objective is to instruct Spring OAuth2 to read and store oauth_clients to/from a database. If we do not need to customize the the default spring oauth database tables to meet our specific needs, then its quite easy and the process is as follow
clientDetailsUserDetailsService
to pass the default Spring OAuth2 org.springframework.security.oauth2.provider.client.JdbcClientDetailsService
and declare the same as a bean, as shown below. Defautlt Spring OAuth2 JdbcClientDetailsService bean definition
and now the clientDetailsUserDetailsService bean should look as follow
And everything else the stays the same. The above changes, will instruct the Spring OAuth2 to read oath_clients from the database (oauth_client_details) rather than hard-coded xml config.
What if, you wanted to modify the default oauth_client_details table to adda few custom columns to meet your specific needs, will in that case you need to make a different set of changes. So we have the following objective
given the following (in the default spring-security.xml)
clientCredentialsTokenEndpointFilter
bean
references
clientAuthenticationManager
bean
references
clientDetailsUserDetailsService
references
clientDetails
We need to transform the above workflow (or chain of references) in order for Spring OAuth2 to be able to access our new customized oauth_client_details database table. So the new workflow or chain of references should be similar to below
clientCredentialsTokenEndpointFilter
bean (no change)
references
clientAuthenticationManager
bean (no change)
references
clientDetailsUserDetailsService
(to be updated)
references
clientDetails
(to be replaced)
And to achieve the above, lets move from bottom to top. The default Spring OAuth2 uses org.springframework.security.oauth2.provider.ClientDetails
to load oauth_client from the default oauth_client_details table. We need to provide a custom implementation for org.springframework.security.oauth2.provider.ClientDetails
by implementing it hence, something as follow
public class MyClientDetails implements ClientDetails { ... }
Before we move on, I just wanted to mention that Spring OAuth2 already provides an implementation for the above interface meaning that we could make our life a lot easier by actually extending the only implementation of the above interface (ClientDetails) which is org.springframework.security.oauth2.provider.client.BaseClientDetails
rather than implementing it from the beginning (leverage all that can be done from BaseClientDetails and add our own custom fields) hence, the MyClientDetails looks as follow then
public class MyClientDetails extends BaseClientDetails {
//fields representing custom column for oauth_client
//getters and setters
//make sure to call super() in the inherited constructors, before
//setting custom fields.
}
Alright, now that we have our own ClientDetails object, just like #2 we need to implement our own JdbcClientDetailsService that will get injected to clientDetailsUserDetailsService
. In order to implement our own JdbcClientDetailsService let's look at the signature of the default method
public class JdbcClientDetailsService
extends Object
implements ClientDetailsService, ClientRegistrationService
As you can see, the above class implements ClientDetailsService & ClientRegistrationService
interfaces. While implementing our own JdbcClientDetailsService I do not recommend extending the default JdbcClientDetailsService because there are quite a few private class variables (and methods) which will not be inherited hence, let's use the default implementation to write our own.
public class MyJdbcClientDetailsService implements ClientDetailsService, ClientRegistrationService {
//override loadClientByClientId method, query the custom
//oauth_client_details database table and populate the
//MyClientDetails object created above and return it.
//Implement other methods to CRUD oauth_clients
}
Now that you implemented MyJdbcClientDetailsService create a bean for it in spring-security.xml, in the following lines
Now inject the above bean to clientDetailsUserDetailsService
, and looking into default spring-security.xml, we have the following
And the above will need to be changed to as follow
There we go, now the chain looks as follow after specifying our own ClientDetailsService:
clientCredentialsTokenEndpointFilter
bean (no change)
references
clientAuthenticationManager
bean (no change)
references
clientDetailsUserDetailsService
(ref updated)
references
myJdbcClientDetailsService
(our customized client details service)
The above instructs the Spring OAuth2 to look up a customized database table for oauth_client details.
JWT Token or JSON Web Token can be used among three different actors of OAuth2 (Authorization server actor, Resource Server actor, Client actor) in order to communicate with each other in performing different actions relating to authorization and access of protected resources. The JWT token is used by Authorization server and it is signed using a public/private key and the objective is to make sure that the content of the JWT token do not get updated (unless someone have access to the private key). The basic workflow can be as follow (Let's assume password grant flow)
Above briefly describes how the JWT token is used by OAuth2. There is a ton of more details on JWT and varieties of JWT out there (signed, signed and encrypted, etc etc).
For more information on JWT implementation of Spring OAuth2, see the official repo here and JWT webpage.
Let's see, we have the following JWT
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
and we would like to include a set of scopes to the above token, so the Resource server actor know whether this user is allowed to make this request or not. The change could look as follow
{
"sub": "1234567890",
"name": "John Doe",
"admin": true,
"scope": "read write delete"
}
To add custom claims (each of above key/value is referred to as claims), we need to create a class MyTokenEnhancer which implement org.springframework.security.oauth2.provider.token.TokenEnhacer
interface and override it's enhance method OR extend org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter
which in turn implements the default TokenEnhancer and override it's enhance method.
By overriding the enhance method, you can add custom claims to the OAuth2AccessToken. Please see the above linked repo for more details on this.
For me, the oauth clients send a customized OAuth2Request and I needed more than the deafult DefaultOAuth2RequestValidator implementation hence, I had to write my own and implemented the org.springframework.security.oauth2.provider.OAuth2RequestValidator
. Something in the following lines
public class MyOAuth2RequestValidator implements OAuth2RequestValidator {
//override the necessary methods
}
Now that we have created our own custom MyOAuth2RequestValidator how should we instruct the Token endpoint to use it using XML configuration? Well, this one took me a little time for figure it out. Looking into the default spring-security.xml we have the following
When we make an OAuth2 request to the /oauth/token
endpoint, the request is mapped to ClientCredentialsTokenEndpointFilter
first, and from there at some stage the request is delegated to org.springframework.security.oauth2.provider.endpoint.TokenEndpoint
and if you look at the source code TokenEndpoint you will see the following
private OAuth2RequestValidator oAuth2RequestValidator = new DefaultOAuth2RequestValidator();
Now to replace that with our Custom validator MyOAuth2RequestValidator
we need to do the following
in the spring-security to use our custom validator from #1 See below the updated authorization-server tag
And that's it. Now, every time the TokenEndpoint is hit, it will use our custom validator instead of default one.
We might not use all of the available grant flows hence, make sense to disable those not used. Let's say, I don't want to use refresh-token grant flow (just an example) and to disable it we have to update the
The authorization server with disabled refresh-token grant flow looks as follow
You can also disable other flows. Obviously you can get away with not disabling it because, if you do not support let's say Authorization Code grant flow then simply do not register an oauth_client that has a value of authorization_code in it's oauth_client_table (if default oauth table)'s authorized_grant_types column.
Also to share with you an experience that helped me understand Spring OAuth2, I actually debugged the Spring OAuth2 classes to understand how each workflow worked and kept track of all the classes that got hit during different grant flows. It's helpful to give you an overall understanding of how Spring OAuth2 works and then it will become less painful to implement your own OAuth2 implementation on top of Spring OAuth2.
If the Resource and Authorization server are not in the same server and not sharing the same database, then Resource server requires to double check with Authorization server after it receives a request for a resource to make sure that the token is still valid (this is useful when tokens don't expire too soon) and has the right permissions/claims. In order to achieve this Spring OAuth provides Check Token Endpoint that can be enabled by adding the following
check-token-enabled="true"
to the
element in the XML configuration file. Once the above is added, a POST request to {server-url}/oauth/check_token
with a form parameter with key token
and value JWT access token
will instruct the Authorization server to validate that the token is valid. The default implementation does the default checks such as
And you will have to do a little bit of customization to validate your custom claims. See my other post in here for more details.