Spring Security allows us to use role-based control to restrict access to API resources. However, inserting role names as simple strings can quickly become cumbersome and increase development cost. Fortunately, we can enclose role details in an Enum and use custom annotations to simplify management of user roles in a Spring Boot application. While it still doesn’t provide type-safe roles, most IDEs will be able to support changes in the code and simplify maintenance.
As an example for this article, I’m going to configure role-based access control. If you want to recreate the work described below, you’re going to need a Spring Boot project with:
- a REST controller with at least one endpoint,
- spring-boot-starter-security and spring-security-test dependencies.
You can find an example project in the spring-boot-user-roles-management repository.
Summarizing the work described below: first I will create an example role and add it to my default user via the application properties. Then I will restrict access to POST endpoints to only users with this role. Finally, I am going to include new access rules in my MVC tests.
Create Enum for user roles
I’m going to start with creating a class for user roles. In straightforward cases, a simple Enum will suffice. However, if we need to encapsulate a role name that does not follow the Enum naming convention, we need to add an additional field for the value. Below you’ll find solutions for both situations.
Roles in a simple Enum
For a simple use case, I’m going to create the following
UserRole Enum with a single role:
In my sample project, I defined a default user with the
spring.security.user.* properties. Now, I have to add the role in my
Thanks to this, the application starts with a default user who already has the authority and I will be able to quickly test my configuration later:
Role name as Enum value
In case we don’t have control over the role names (e.g. they are created in an external authorization service like Keycloak and may look like
chief-operation-officer), we can still provide a convenient mapping. We just need to encapsulate the external name in an Enum value and override the
As before, I need to add the role to my default user defined with the
We can define role-based security rules in the HttpSecurity configuration or for each method / class. Below you’ll find example implementation for both cases.
Specify access rules in the HttpSecurity config
In the following HttpSecurity configuration, I’m going to allow only users with the required role to access POST endpoints:
As a result, POST endpoints will be inaccessible to anyone except a
As a side note, I’m using the
authorizeHttpRequests() method because it seems that autorizeRequests() will be deprecated. Furthermore, you can use
mvcMatchers() instead of
antMatchers() if you need a different approach to pattern matching, like in the snippet below:
Verify user role at the method level
When we need more precise access control, we can apply method level security. First, I’m going to enable method security using the following class:
Annotate your class with
@EnableGlobalMethodSecurity if you use Spring Security version < 5.6.
Next, I’m going to add the
@PreAuthorize annotation onto the example POST endpoint:
Unfortunately, we have to use the fully-qualified path in the SpEL expression. Obviously, I don’t want to clutter controllers with such low-level details. Therefore, I’m going to create a custom annotation.
Create a custom annotation to hide irrelevant details
Luckily, creating a meta annotation is simple:
Let’s take a look at the annotations I’m using here:
- @Target – puts constrains on the usage of the annotation. The TYPE
Element Typeallows using it on a class (e.g. controller classes), interface, Enum or record declaration. I’m also providing the METHOD as a possible target to allow using this annotation on a single endpoint.
- @Retention – specifies how long an annotation is to be retained. The RUNTIME Retention Policy enables the annotation to be retained by the VM at run time.
- @PreAuthorize – the original annotation that I want to remove from the controller code.
As a result, all irrelevant details remain hidden from my controllers:
By keeping user roles in Enum and using them in annotations as described above, we can take advantage of our IDE when maintaining the authorities.
Unfortunately, I haven’t found a convenient method of using Enum when mocking a user role in tests. Therefore, as we can see in the snippet below, we still have to provide the required role as a simple String:
To simplify maintenance of the tests, we can create our custom annotation to mock users with the CHIEF_OPERATING_OFFICER role:
For more advanced mapping, when we keep the actual user role in the Enum value:
As a result, we don’t have to provide user role as a string in every test class or test method:
Read more on simplifying roles management in Spring
- How to create typesafe user roles for Spring Security?
- Spring Security @PreAuthorization pass enums in directly
- Keycloak with Spring Boot #4 – Simple guide for roles and authorities
Photo by Kindel Media from Pexels