SpringBoot: Embrace AOP For Authorizing API Requests
In today’s article I will talk about how to leverage Spring AOP in order to authorize API requests at endpoint level.
Disclaimer: The purpose of this article is to give you a practical example of using AOP and not to explain in details all concepts.
Background
Let’s suppose we build an API to track monthly expenses which has Spring Security with basic auth enabled and we want to authorize the requests based on the authorities of the authenticated user.
As a short parenthesis, authentication is the process of validating user’s identity to determine they are who they claim to be and authorization is the process of validating the user’s permissions/roles/rights in order to access a specific resource.
For simplicity we have only two authorities: USER
and ADMIN
and we can consider the authentication process already populates the Spring Security Context with correct granted authorities based on username/password combination.
The Problem
Some of our API’s endpoints require USER
authority and others require the ADMIN
one (for user management and other administrative requirements).
How can we achieve authorization for our custom authorities?
The Out-of-the-box Solution
With Spring we can use the well known @PreAuthorize
annotation at endpoint level:
As you can see we specify the hasAuthority('USER')
as value for the @PreAuthorize
annotation which translates to: the authenticated user must have the USER
authority in order to access this endpoint. If this authority is missing then 403 Forbidden will be returned.
The main issue with specifying the authority value as plain text is the maintainability and the fact that is prone to typos and breaking changes. Imagine we want to refactor the authority name from USER
to CUSTOMER
in SecurityAuthorities
…this means you need to make sure you find all places where 'USER'
string is found and replace it with 'CUSTOMER'
; add the fact that you need to do it in 25 different places and it quickly becomes a pain. So why not using our enum class instead?
Using the enum class is a little bit easier than plain text because at compile time you can put the correct value after the fully qualified package name and you don’t need to bother with typos. Also, if you rename the authority name or move the enum in another package the changes will be reflected here…BUT if you delete the enum the compiler will not complain at all and this is a big issue because it hides the fact your endpoint is expecting an authority or authorities which don’t exist anymore.
Even if the @PreAuthorize
annotation solves the authorization process and it’s pretty straightforward to use we still need a cleaner and more maintainable solution in order to use our enum values directly without qualifying package names or hardcoded strings and at the same time to be compile time safe. AOP comes to the rescue!
The AOP Solution
What is AOP
Aspect-Oriented Programming (AOP) complements Object-Oriented Programming (OOP) by providing another way of thinking about program structure. The key unit of modularity in OOP is the class, whereas in AOP the unit of modularity is the aspect. Aspects enable the modularization of concerns such as transaction management that cut across multiple types and objects. — by Spring Docs
I know the definition given by Spring Docs can be a little hard to grasp on but long story short AOP allows you to add additional behavior to existing code without modifying the code itself.
What we will do basically is to implement a method which will be invoked by AOP automatically whenever a new request comes to our endpoint and which receives a set of authorities to check against the authenticated user (the Principal
) in order to decide the outcome of the authorization. Keep in mind the existing code for our endpoint will suffer one minor change only: replacing the @PreAuthorize
annotation with a custom one. Let’s proceed…
Custom Annotation
First, we need a way to specify which subset of our custom authorities does the endpoint need to check. For this we will create a custom annotation to work directly with our enum and not strings anymore:
Next we need to update our endpoint(s) as follows:
The nice thing about this custom annotation is that in case we refactor our SecurityAuthorities
enum all changes will be reflected immediately and in case we delete it the compiler will scream.
AOP Method
After we defined our custom annotation we need to create the method that will be invoked on each request targeting one of our endpoints annotated with @HasEndpointAuthorities
.
Notice the @Aspect
annotation at class level and @Before
annotation at method level. The first one marks the class to be used by Spring AOP and the second one is translated as follows: invoke hasAuthorities
method before each method call annotated with @HasEndpointAuthorities
which is part of a class annotated with @RestController
only. So whenever a request comes to our /api/v1/expenses
endpoint, the hasAuthorities
method will be automatically invoked before the actual call to the getExpenses
method. If hasAuthorities
throws an exception (403 Forbidden) then getExpenses
will not be invoked anymore and the 403 response will be automatically returned.
That’s it! You can control now which authorities to check for each endpoint by simply using one line of code via the custom annotation and you can fully benefit of the enum class also.
Conclusion
As always please keep in mind this approach might or might not suit your project context or needs and I’m not in the position to say there’s no other way to do it differently or better. I really hope you enjoyed it and had fun reading it.