Keycloak with Spring Boot #4 – Simple guide for roles and authorities

featured image

Delegating user management to Keycloak allows us to better focus on meeting the business needs of an application. However, we still need to provide the appropriate configuration to translate user roles and privileges between Keycloak and Spring Boot. Additionally, we’re going to need some handy techniques for debugging how roles are converted between the two services.


First, I will describe some debugging techniques. Next, I’ll show you some sample role mapping setups.

Debugging role mapping between Keycloak and Spring Boot

I’m going to show you some useful methods for verifying role support in a Spring Boot app and Keycloak server. By seeing what is really going on under the hood, you can save a lot of time when an apparently correct configuration does not bring the expected results.

How to debug roles in Spring Security Context

We can look into the Spring Security Context to verify the actual permissions that are being assessed in two ways:

  • Set the security logging level to DEBUG – add the property to the file. Thanks to this, after each API request you’ll see a list of Granted Authorities in the logs:

As a result, when you call this endpoint, you’ll see log entries similar to these:

How to debug roles sent from Keycloak

In my example realm, there is a default user christina who has the roles of user and chief-operation-officer. We can verify her privileges by copying the token from Postman or from the developer tool in the browser as seen in the screenshots below:

Then, decode the token value in e.g. tool to see the actual content:

token with user roles from Kecyloak

Roles vs Authorities

Spring Security allows us to configure privileges in a very granular manner with Authorities (e.g. CAN_WRITE). However, we can also manage resource access in a more coarse fashion with Roles (e.g ROLE_EDITOR). In other words, the ROLE_ prefix is what differentiate these concepts in Spring.

Keycloak recognises this naming convention. Therefore, we can decide whether we want to map privileges defined in our Keycloak server as roles or authorities in Spring Boot.

Mapping Roles

To map my chief-operating-officer Keycloak role to ROLE_CHIEF-OPERATING-OFFICER in Spring Boot, I’m going to use SimpleAuthorityMapper:

As we can see in the SimpleAuthorityMapper documentation, the default prefix is ROLE_:

Documentation of role mapper from Spring used by Keycloak

Therefore, we don’t need to set it manually.

As a result, I can impose access restrictions to selected resources. Below you can see the role used in the @PreAuthorize annotation:

On the other hand, I can restrict access to all POST endpoints only to users with this role:

In both cases, the hasRole expression will automatically add the ROLE_ prefix to the given CHIEF-OPERATING-OFFICER value and compare the result with the ROLE_CHIEF-OPERATING-OFFICER GrantedAuthority value which we have mapped in the Spring SecurityContext. This behaviour is described in the method docs:

Spring Boot method for evaluating user role

Mapping Authorities

If you want to control access in more detail, you can map the permissions from Keycloak to Authorities in Spring Security. Remove the ROLE_ prefix when configuring the authority mapper:

This makes the sample can-write permission from Keycloak become CAN-WRITE authority in Spring SecurityContext.

Below you can see the authority used in the @PreAuthorize annotation:

Then again, I can restrict access to all POST endpoints only to users with this authority:

In both cases, the hasAuthority expression will compare the given CAN-WRITE value with the CAN-WRITE GrantedAuthority value which we have mapped in the Spring SecurityContext.

If you configure the authority mapper in a way that removes the ROLE_ prefix from the authorities, don’t use hasRole in security expressions and configuration.

Realm roles vs client roles

You can read about differences between the realm and client roles in the Keycloak in Docker #4 – How to define user privileges and roles article. Given that the Keycloak server contains properly defined user roles, we need to instruct our Spring Boot app which role set to evaluate. We’ll do so with the keycloak.use-resource-role-mappings property.

Evaluate realm roles in Spring Boot

A Spring Boot app will evaluate the realm roles if the keycloak.use-resource-role-mappings property is set to false, which is the default value. You can verify that the values from the realm-access field in the access token are present as GrantedAuthirities in the SecurityContext using the debug methods shown above.

Evaluate client roles in Spring Boot

In the keycloak.* properties, resource is the client. Therefore, by setting keycloak.use-resource-role-mappings to true, we’re telling Spring Boot that the client roles should be considered. You can verify that the values from the resource-access field in the access token are present as GrantedAuthirities in the SecurityContext using the debug methods shown above.

Handle user roles in Spring Boot tests

When we restrict endpoint access to a specific role, we need to update Spring MVC tests to keep them working properly.

Custom annotation for a mocked user

Fortunately, Spring provides us with the @WithMockUser annotation that allows us to test an endpoint for a given user role, e.g.:

In the same manner we can mock a test user with a given authority:

However, having to manually enter a role name for each test case or test class where that role matters would be cumbersome. Instead, we can create a custom meta annotation:

And then use it in test cases (or classes) like in the example snippet below:

Enable method-level security

If we’re using annotations like @PreAuthorize to restrict access to endpoints, we have to create an appropriate configuration class for our MVC tests:

Then we have to load it when running the tests. We can achieve it by adding @Import or a custom annotation to the test classes.

Read more on handling Keycloak roles in Spring Boot

Photo by Robert Nagy from Pexels

Leave a Reply

Your email address will not be published. Required fields are marked *