Implementing OAuth 2.0 access token validation with Spring Security

Wojciech Krzywiec
19 min readMar 15, 2021

--

Would you like to know some basic concepts of Spring Security that can be implemented in a modern, micorservice application? If so this article is for you! In it I’ll guide you how to add step-by-step OAuth 2.0 access token validation to REST API endpoints of your Spring Boot application.

This is part 3 of my series on OAuth 2.0 in which I’m describing how OAuth 2.0 works and give an example implementations of key actors. If you’re looking for theoretical introduction to it, go check my first blog post — Introduction to OAuth 2.0.

In a second article of this series I’ve described how to set a Keycloak server which works as an authorization server and in short is responsible for issuing an access token. It’s not necessary for you to read a previous article, but be aware that you might require to do that if you would like to manually test the authorization logic of what will be build in this post.

After this introduction, let me described what will be build in this post.

Movies backend application

To make it simple I’ve prepared a small Spring Boot application which servers two REST endpoints to the outside world:

First one /movies provide a list of all movies and the latter, movies/{id}, provides a single movie information if you provide it's id. Quite simple.

And now the goal is to protect both endpoints so only authorized user and external applications (clients) can use them. To achieve it we will force all consumers of the API to provide a valid JWT access token in each request.

And what does it mean a valid access token?

The answer to this question would be probably different and depends on a case, but for my simple project, a valid token is when:

  • its type is JWT,
  • its signature is correct (it assures that nobody has changed a content of a token),
  • it’s not expired,
  • it contains roles and scopes information.

A valid token is not everything. In order to consume each of these endpoint inside the access token a certain role should be present. In example, only users with ADMIN role should be allowed to get information from the /movies endpoint and only users with VISITOR role can get information from a second endpoint. Therefore I would like to establish a different validation which will check if certain role is present inside an access token.

And these are all features that will be covered in this article, so let’s start implementing it!

Access token validation with Spring Security

Like many other problems this one could be resolved in many ways. For instance it would be possible to not use Spring Security at all and implement everything from the scratch. Another approach would be to use some additional library, built on top of the Spring Security which would magicaly do most of the job, but possibly it won’t allow to do some custom stuff.

For learning purposes I’ve decided to do something in the middle. I’ll use Spring Security, but most of a configuration and validation I’ll do by myself. If you think that my approach add complexity, you’re probably right. After all I could use some open sourced libraries, like keycloak-spring-boot-starter or spring-boot-starter-oauth2-resource-server, which would do the job with only couple lines of code. But again, this project was for me to learn something new and in your project you can select the solution that is the most suitable for you.

Ok, enough of all these explanations and introductions, let’s code something!

First step, would be to add Spring Boot Security starter dependency to the pom.xml (if you use Maven):

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.3.4.RELEASE</version>
</dependency>

If you now run the application and try to use one of the endpoints, let’s say /movies, you will be redirected to the login page:

Cool! By simply adding this library our endpoint is now secured! Only authorized can use this endpoint. But is it what we would like to achieve? Is it OAuth 2.0 approach? Unfortunately not.

Before going to the solution let’s first add necessary libraries right away:

<!-- OAuth 2.0 dependencies -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.11.0</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>jwks-rsa</artifactId>
<version>0.15.0</version>
</dependency>
<!-- Utils dependencies -->
<dependency>
<groupId>org.zalando</groupId>
<artifactId>logbook-spring-boot-starter</artifactId>
<version>2.4.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.0-jre</version>
</dependency>

So how to tackle this problem? We need to change a default Spring Security configuration but first let me briefly explain the concept of Filters.

The main idea behind filter in Spring Security is build on top of the aspect-oriented programming, in Spring it’s Spring AOP. In short, when any HTTP request is received by an application first thing that Spring does is it pass it first to the one or many filters (FilterChain). Those filters are components that can validate incoming request and block it if something is not right, so that the request will never reach a controller.

Therefore our task is quite simple — create a new filter that will check if in the incoming request there is an Authorization header and then validates if it contains access token in a proper format.

Our custom filter needs to extend AbstractAuthenticationProcessingFilter class and override two methods: attemptAuthentication and successfulAuthentication.

Both methods name are self explanatory, but in short in a first one we will put a logic for validating if in an incoming request there is valid access token and in the second one there will be the logic of what will be done when the authentication ends with success.

And here is the implementation of both methods:

Starting from the top you can see that this class requires two other dependencies, provided in the constructor — JwtTokenValidator and AuthenticationManager. First one is a custom class that encapsulates entire logic of validating access token, I'll show it just in a minute. The second is a Spring Security interface which is responsible for processing of Authentication object that is a result of attemptAuthentication if everything went successful. I'll mention it once again, when we'll move to configuration, but now, let's move on to the JwtTokenValidator.

The only public method of the validator is validateAuthorizationHeader in which we need to pass a content of the Authorization header that should be part of the incoming request.

The result of a validation is an AccessToken object, which is a holder of a raw token, plus it has a helper method that returns a Collection of GrantedAuthorities, in our case Keycloak roles.

Going back to the JwtTokenValidator, first step in validateAuthorizationHeader is to extract a token from Authorization header as a String with a private method subStringBearer:

The reason for that is becuasue the schema of Authorization header looks like this: Bearer <token>, therefore we need to remove the Bearer part. What remains, which is token itself looks as follows:

eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJXYmdnRkNBNzJ4UG5KYWNUZTRHMzdEN1NDWFpJOW8wQVZMTWd0d0tfamhRIn0.eyJleHAiOjE2MTM2Mjc1NzYsImlhdCI6MTYxMzYyNzI3NiwianRpIjoiZjkzODVlMDMtYzRjOC00NzY0LWIyMDgtYzBhYWFiMjIyODEyIiwiaXNzIjoiaHR0cDovL2tleWNsb2FrOjgwODAvYXV0aC9yZWFsbXMvdGVzdCIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiIwODhjNTdiMy1mNGJlLTQwOTQtOGQzNC00Y2UyNWQ1OGUzY2IiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJ0ZXN0X2NsaWVudCIsInNlc3Npb25fc3RhdGUiOiJlMTYxMTNjZi0wZGI5LTRlZDMtOGJjMC0yNWRmNDY4MDU2NjkiLCJhY3IiOiIxIiwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJ0ZXN0X2NsaWVudCI6eyJyb2xlcyI6WyJ1bWFfcHJvdGVjdGlvbiJdfSwiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJvcGVuaWQgZW1haWwgcHJvZmlsZSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiY2xpZW50SWQiOiJ0ZXN0X2NsaWVudCIsImNsaWVudEhvc3QiOiIxNzIuMTguMC4xIiwicHJlZmVycmVkX3VzZXJuYW1lIjoic2VydmljZS1hY2NvdW50LXRlc3RfY2xpZW50IiwiY2xpZW50QWRkcmVzcyI6IjE3Mi4xOC4wLjEifQ.ihCQGFWRdr1NR7el7zsnnXZFwonOpFgEE_MHBlYSq07s2JhjsLJauvQ9erTM5YV_gmY-Q-QpmpRI-qq4miF9hem8qxXzxBkei7cXyYYQeiz44MwkusUW75VChfsYljgRNUSJM5G4_O636xWQNc2ET5v8508CPpgVIvl4QFKYQui3J2BADH8AyDihgaOcF5hfrutuEpH6AINvBmUebUKOLG3ZyST81Q3GjmSZmBaL7RK29uTm94i1HVqfXgRLIuf5zxLucq_gU4KM8c3mB5d8ZfJOMl8nOnYDbEbiGVEP_coz9x3iF2Lf3Fp8K2zez59w4yvGTy39Whns-KhtX02yAQ

Not very informative, isn’t it? No problem, we can decode it using for example jwt.io page:

It contains lots of information, like user information, including roles. If you want to know more about JWT and tokens in particular, go check my previous blog post where I’ve introduced all of these concepts —Introduction to OAuth 2.0.

Next, having a raw token we can make a real validation of it in a new private method:

And here a first step is to decode gibberish token into understandable Java object, therefore we use a convenient library — com.auth0:java-jwt, which returns a DecodedJWT object.

Next, having token decoded we can verify its content and starting from the header we would like first to validate if this is a JWT token.

Moving on, we have now very important part — validating a signature. If a signature will not be valid it will mean that somebody has done something funky with a token. Maybe an attacker tried to modify its payload to get to the wanted resource? That’s one of the possibilities from which signature is trying to protect.

In short, signing is a cryptographic operations in which as an input header, payload and special secret are provided and the result is a signature. If anything changes in the any of these three elements, singature needs also to be changed. There are variuos ways to validate if signature is correct and one of them, which I would like to use is RS256 signature. With this approach we’ve got a pair of keys (secrets), first is used for creating a signature and a second could be used only for validating if a signature is correct.

And that is we would like to achieve, to verify if a signature is correct we need to somehow get a public key (JSON Web Key — JWK) and verify it with what is inside the token.

In above code snippet you can see that there are two objects that might be a little mistic — JWK and jwkProvider. First one is a representation of a public key and the second one (which is an implementation of an JwkProvider interface) is responsible for getting it. Both classes are defined in a com.auth0:jwks-rsa library.

The JwkProvider is just an interface therefore we need to write our own implementation. In our case a provider of the public key is Keycloak (which is also generating tokens), therefore the relevant class was named KeycloakJwkProvider.

The JwkProvider defines only one method public Jwk get(String keyId) which returns JSON Web Key (JWK) representation by a given kid which is available in the header of the access token. It's an identifier of a public key if an authorization server supports multiple keys.

In the constructor of this class there is jwkProviderUrl String which represtens an URL that needs to be called by an application to fetch a JWK. And what KeycloakJwkProvider is actually doing is using this public URL to fetch JWK(s) and produce a Java Jwk representation of it. An example of such URL for Keycloak would be:

{keycloak_base_url}/auth/realms/{realm_name}/protocol/openid-connect/certs

where keycloak_base_url is Keycloak's base URL (in my case localhost:8080) and realm_name is the name of a realm (in my case test).

An example of a response from such endpoint would be:

{
"keys": [{
"kid": "WbggFCA72xPnJacTe4G37D7SCXZI9o0AVLMgtwK_jhQ",
"kty": "RSA",
"alg": "RS256",
"use": "sig",
"n": "k96-hliB-mYJx47V4wPqnWi89IsSYfmCXjiWgHG4e4VAEcLqZ33V9aQ3nConMf0AWbG9zcQZUtqSTtXVKeUvtx_7QH9Rgzwoj0XDOU3K21hcZCs7ShMK-2kROk2jnZpM5I6r7GeloR52fPXIck-sXcR3acmeUgZyR_y7TtlYYCn4GUt2kjrwyL_qdNdvQoDXvHTnxv4wk3_-Zff7I3xK3lnPE8uIz6gxjFMZ20-ruU_5-umpUvF9B6NUPf3LQTlMm864MBw1narN6QrWTp9c9DF0SeGcN8r_XMo0jDkMDjTWwrHiumnA8Il50hbmcp_WdMV8ivuVoONGLc_5Q6-uUQ",
"e": "AQAB",
"x5c": [
"MIIClzCCAX8CBgF3GcqCYDANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDDAR0ZXN0MB4XDTIxMDExOTA4MzUzOFoXDTMxMDExOTA4MzcxOFowDzENMAsGA1UEAwwEdGVzdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJPevoZYgfpmCceO1eMD6p1ovPSLEmH5gl44loBxuHuFQBHC6md91fWkN5wqJzH9AFmxvc3EGVLakk7V1SnlL7cf+0B/UYM8KI9FwzlNyttYXGQrO0oTCvtpETpNo52aTOSOq+xnpaEednz1yHJPrF3Ed2nJnlIGckf8u07ZWGAp+BlLdpI68Mi/6nTXb0KA17x058b+MJN//mX3+yN8St5ZzxPLiM+oMYxTGdtPq7lP+frpqVLxfQejVD39y0E5TJvOuDAcNZ2qzekK1k6fXPQxdEnhnDfK/1zKNIw5DA401sKx4rppwPCJedIW5nKf1nTFfIr7laDjRi3P+UOvrlECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAN+QZvSPXUP6Q9JxpYOzl6U7TXLAWmQPznf+gdSc4rmHQD5gKr2s8zeNuJrx+cIIxPGsMk04VGdNjKf8bRYvB7ta+POxQ5VRnHZS5XuEyGBFeSWbbzwZnQY+cWjY3AmyReenJ4QKal4iFZgNvkYbVaw9SX1qDXTXQTn6OXyz/Cp8SbMUW8fy7GqdGFLGKYhX1Irq4Reiidkw4pKVgGHblcjB2hIRfpC7TYnv0jL9RK8iymvPGYRQYka2ks8J/+HNudKO8MoOwghtP9jwTHEygioc9gAyf8DMS9cGnsFnisCz4B/etTyvyzuhyBgTili5WzXjOk1Y0NqaxY50618BvPg=="],
"x5t": "KQ4zgovMGPVesO36mzxvI9UTfdQ",
"x5t#S256": "F4xuZVX7WsxcdXAetwwTOOI2ElV-hH9Gl_G25hANAaE"
}]
}

Let’s go back to the JwtTokenValidator class, to the verifySignature(DecodedJWT decodedJWT) method. Thanks to the KeycloakJwkProvider we've got a JWK representation, therefore we can now do a validation of a signature with a public key. For that we again use helper classes from the com.auth0:java-jwt library first to select a signing algorithm and then to do a verification.

Great! After validating that a signature is correct we can be sure that all information inside of the payload are secured. Before moving on, we need also to validate a payload if a token has not expired, holds user roles and has scope information.

Before verifying a payload of a token, first I’m decoding it to the Gson’s JsonObject:

One of the fundamental rule of OAuth 2.0 framework is the fact that an access token should be a short live creature, usually for couple of minutes. It is because it may be a possibility that an attacker stills an access token and gets a pernament access to the resource owner. To reduce a blast radius access tokens are short lived so even if this situation occur an attacker will have an access only for a very limited time.

In a payload of a token I’m expecting a exp claim (field) that will hold information until when a token is valid, which is a standard way of adding such information to JWT.

Next point is to validate if inside of a token there are information about user roles that were assigned in Keycloak. I want to do this validation because we’ll require them for the REST endpoints.

Roles in JWT generated by Keycloak are inside of the roles array field, which is part of the realm_access object field.

And the last thing that I want to check if inside of a payload there is a scope field.

At that’s a full implementation of JwtTokenValidator. I think that it's more than enough validation for a start, but it could be easily extended if there will a need to do that.

The last thing that I haven’t mentioned yet is InvalidTokenException class, which is a custom exception object which I want to throw whenever something wrong is with an access token.

It extends a Spring Security abstract class AuthenticationException which is a superclass for all exceptions related to authentication.

Let’s go back to the AccessTokenFilter class from which everything has started. After validating incoming access token as string JwtTokenValidator returns an AccessToken object, but the attemptAuthentication must return an Authentication object, therefore I've created a wrapper class that extends AbstractAuthenticationToken (which extends Authentication interface) and holds AccessToken object.

And an implementation of attemptAuthentication method will be:

The logic of creating and Authentication object has been described. What has left is to implement the logic successfulAuthentication method which will be invoked if previous one will end with success.

The implementation of this method is quite straightforward, after successful authentication an Authentication object (in our case it's JwtAuthentication class) is added to the SecurityContextHolder, which stores all information about the current user user who is making a request.

All right, all the building blocks have been created, now, let’s put it together in a configuration class to enable it in an application. To do that let’s create a SecurityConfig class that will extends WebSecurityConfigurerAdapter, which allows us with only few lines of code to make a security configuration. The WebSecurityConfigurerAdapter has multiple methods that could be override to make magic happened.

Let’s break above config into smaller peaces to understand it better.

  • @Order(1) annotation - as I mentioned before, one of the key concept of Spring Security are filters that could be combine into a chain. If we need to define multiple filters we can specify which one will be used in which order. By adding this annotation we tell that our filter has the biggest priority (a lower the number the bigger priority),
  • @EnableWebSecurity annotation - this annotation tells Spring that this is a configuration class and enables security features defined by WebSecurityConfigurerAdapter superclass,
  • String nonSecureUrl field - holds information which of served URLs by the application should be excluded from checking the access token. Those values (comma seperated) can be passed to application.properties or application.yaml file under spring.security.ignored key. For my application I've decided to exclude all actuator endpoints to allow external services to collect metrics.
  • Url values defined in above variable are than used in the configure(WebSecurity web) method, where we configure Spring Security to ignore validation for provided endpoints
  • String jwkProviderUrl field - this holds a base URL of the authorization server, Keycloak in our case, that provides a public key needed for validating signature of an access token,
  • value that is holded by above variable is used to create a Spring bean in JwkProvider keycloakJwkProvider(),
  • configure(HttpSecurity http) - this is the most important method in this class, because here we're applying the AccessTokenFilter to the filter chain with addFilterBefore method. Apart from that I've also decided to add following configs:
  • .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) - I would like to have a stateless application, which means that user info are not stored in-memory between requests and prevents Spring from creating HTTP sessions,
  • .csrf().disable() - because I want to have a stateless and relatively simple application I decided to disable CSRF protection, if you need to know more about protecting from this kind of attacks go check the Spring official docs
  • cors() - with this config we will allow other origin (domain, server, port) to use endpoints; in simplier words if we would not add it our frontend app (that will be build in upcoming post) would not be able to consume REST API because it'll be running on a different port (when I'll start it locally),
  • AuthenticationManager authenticationManagerBean() - here we're defining the AuthenticationManager bean,
  • configure(AuthenticationManagerBuilder auth) - having already the AuthenticationManager we need to register an AuthenticationProvider, which is defined as a bean in following config method,
  • AuthenticationProvider authenticationProvider() - in our case we would like to define KeycloakAuthenticationProvider as a provider,
  • JwtTokenValidator jwtTokenValidator(JwkProvider jwkProvider) - here we're defining the class responsible for token validation.

The last thing that we need to set up is to assign some variable in application.yaml (or if you prefer application.properties) file.

First property are for indicating which endpoints should be excluded from access token verification. Second one points out to the endpoint from which public keys could be fetched. Third one indicates on which port I would like to run my application (it’s not a default, because I run my Keycloak instance on 8080 port).

Manual testing

In order to test E2E our solution we will need a Keycloak instance up and running. In my previous blog post I’ve described how it can be achieved using Docker, but if you don’t want go back here, in short are what we need to have.

First of all clone my repository — keycloak-security-example inside of which there is a docker-compose.yaml. The interesting part is the following:

With these two services (keycloak and postgres) you can start up Keycloak with predefined realm, client, users and roles. To do that just run the command:

> docker-compose up -d keycloak
Starting postgres ... done
Starting keycloak ... done

To test it together with an application there is still one more thing. We need to adjust hosts file, which location depends on the OS:

Once you found it add following line:

127.0.0.1	keycloak

This step is necessary because the address of Keycloak server is different depending on the point of view. From the point of view of an app running in the web browser it’ll be http://localhost:8080, but from the perspective of backend application that is running inside the Docker network it will be http://keycloak:8080. To overcome it above config is necessary.

To test if everything is working go to the http://localhost:8080 and you should see Keycloak's admin panel (username: admin, password: admin).

Now, let’s test our backend application! First let’s see if /actuator endpoint is really not protected, therefore using web browser, Postman or whatever other tool you like.

All right, it’s not protected. Fine. Let’s now check other two endpoints — /movies and /movies/1.

In both cases we’re receiving the 401 Unauthorized response, great! Let’s now get the authorization by having an access token.

To achieve it we will need to make a request to the token endpoint exposed by Keycloak — {keycloak}/auth/realms/{realm}/protocol/openid-connect/token, which in our case is http://localhost:8080/auth/realms/test/protocol/openid-connect/token.

To get an access token we need to pass credentials. Accordingly to the OAuth 2.0 flow there are multiple ways to get an access token. I’ll use one of the simplets grant type — password. With it we need to provide only for which scope we would like to be authorized together with client_id and client_secret. Moreover we need to have a user credentials - username and password.

If you’re running Keycloak using my docker-compose.yaml file you can use following credentials, but if not I’m describing how to find it on Keycloak UI.

client_id       test_client
client_secret 8ac27a39-fa84-46b9-8c30-b485056e0cea
username luke
password password

client_id is a name of an application (frontend) and client_secret can be found after logging in to the Test Keycloak realm and by clicking following links Clients > test_client > Credentials.

If you see only **** characters just click Regenerate Secret button and a new one will be created.

To create eligible user go to the User menu and click Add user button which will guide you thru very short user registration. To set up password after creating user go to Credentials tab and set a new one. Make sure that Temporary toggle is switched off.

Having all these information we can now use POST HTTP method to already mentioned URL inside the body and send it with a content type application/x-www-form-urlencoded.

As a result we’ll receive a JSON response with bunch of information including access, identity and refresh token token.

Having an access token copy it now to a new request that will be made to our backend application (let’s say /movies endpoint). It needs to be added to a HTTP header called Authorization where value will be Bearer <token>.

Awesome! We’ve got information about favourite movies! 🎉

Handling AuthenticationException

In the test case when we haven’t provided an access token our application returns only the HTTP code 401, which is kind of an information but still it’s not really verbose. It doesn’t tell why request was not authorized.

Luckily we can easily fix it by creating our custom AuthenticationFailureHandler and add it to the AccessTokenFilter.

Let’s first create a handle class — AccessTokenAuthenticationFailureHandler which implements AuthenticationFailureHandler interface.

AuthenticationFailureHandler interface has one method that is here implements. It'll be called when the AuthenticationException is thrown (in our app InvalidTokenException is extending this abstract class) and in it a response is modified to return 401 UNAUTHORIZED HTTP code with a JSON body with an explanation.

Once we’re having it we need to assign it to the AccessTokenFilter by adding an extra parameter to the constructor.

Method setAuthenticationFailureHandler(AuthenticationFailureHandler failureHandler) is defined in the superclass AbstractAuthenticationProcessingFilter from which AccessTokenFilter inherits.

Finally we need to modify SecurityConfig class to define new exception handler as a Spring bean and add it to a AccessTokenFilter constructor.

Now if we run an application once again and for example provide an expired token to the request we should get nice looking information what went wrong.

Protecting REST API endpoints by user role

Next step into full-secured application is to allow only selected users to use certain REST API endpoints. In order to define those special users we need to assign roles to their accounts. In my previous article about Keycloak set up I’ve created two roles and assign each on of them to different user. Now looking at the movie backend application let’s say that an endpoint /movies can be used only by a user with ADMIN role, and an endpoint /movies/{id} only by the users with VISITOR role. That's the target.

To achieve it let’s create a custom aspect annotation which we can than add to the controller method so that before invoking it we will check is a user has valid roles inside of an access token.

Therefore, first we need to creat an annotation AllowedRoles:

Then, we need to create an Spring aspect (remember to add spring-boot-starter-aop dependency to your Maven/Gradle definition) that will do all validations:

What’s happening here? First we get a list of roles that certain method requires from the value passed to the annotation. Than we gets from SecurityContext the list of roles that user is assigned to. And finally we check if a user has all required roles. If yes, everything is fine. If not an AccessDeniedException is thrown.

Finally we can add annotations to the controller methods:

Like before, to gently handle an authorization exception (AccessDeniedException to be precise) and to inform the user why she/he can't access certain endpoint we need to be create a new kind of a handler, which will be almost a copy-paste from AccessTokenAuthenticationFailureHandler. This time our AuthorizationAccessDeniedHandler will be implementing the AccessDeniedHandler interface.

It also requires to amend the SecurityConfig class with exceptionHandling()..accessDeniedHandler(...):

Now, if we test the /movies endpoint using luke's (VISITOR role) access token we should get follwoing result:

Awesome! Apart from having information that user is not allowed to get access to the specific resource our backend application also returns the reason.

Unit and E2E tests

That could be the end of this blog post, but as it’s a good practice to have tests for a code we write let’s do some of these.

Becuase most of the things that we created are requiring the entire Spring context most cases that we will write will actual E2E test, where we spin up our application together with Keycloak and run some tests. But there is a small part of the code for which we could write fast unit tests — JwtTokenValidator.

As you may already forget (yes I know that this post is rather long), JwtTokenValidator is a class which holds the entire logic of validating an access token. As an input it takes a value from Authorization HTTP header and returns an AccessToken object. It doesn't need to have an entire Spring context up and running, and it's the most complex part of what was written, which needs multiple test cases to be checked.

Units test for the JwtTokenValidator class I'll write with Spock test framework (Groovy language). If you don't know it, don't worry. I've tried to keep it simple and it's not that far from good old Java.

In all these tests first we generate an access token (valid or not) using com.auth0 libraries mentioned before and than checking if an AccessToken object was created or an exception was thrown with specific message.

For broader and longer tests I have decided to go back to Java’s JUnit5 and use Spring’s MockMvc. Becuase the entire application will be spinned up we need to somehow spin up Keycloak. Luckily we can use Testcontainers which allows to run it inside a Docker container, similar to the one that is defined in the docker-compose.yaml (with small change of database type).

All the tests are pretty straightforward. First we gets a new access token for particular user, attaches it to the request’s header and check the response.

That would be it! With this long blog post I’ve tried to show you how to implement OAuth 2.0 a protected resource application with Spring Boot that will work with popular identity provider. I hope you’ve learn couple of things about Spring Security or OAuth 2.0 in general so I can invite you for the last part of my short series where I describe how to consume protected resource in Angular application.

Code for entire project can be found in my GitHub repository:

--

--

Wojciech Krzywiec
Wojciech Krzywiec

Written by Wojciech Krzywiec

Java Software Developer, DevOps newbie, constant learner, podcast enthusiast.

Responses (5)