In this tutorial, you will learn how to use the @PostAuthorize annotation to secure the return of the method’s return value in your Spring Boot Application. The @PostAuthorize annotation is evaluated after the business logic in a method is executed and if needed will prevent the method from returning a return value.
There are other useful method-level security annotations like the ones below. It is useful to know how they work as well.
- @PreAuthorize Security Annotation Example,
- @Secured Security Annotation Example,
- Spring Method Security: Customize Error Message.
If you are interested in video lessons, then I also show how to create user Roles and Authorities and how to use Spring Method Level Security annotations in my video course: RESTful Web Services, Spring Boot, Spring MVC, and JPA.
Enable Spring Security
To be able to use the @PostAuthorize annotation in your Spring Boot application you will first need to enable Spring Security in your project. To enable Spring Security in your Spring Boot application add the following dependency to the pom.xml file.
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
Now when you have added the Spring Security support to your Spring Boot application, you can proceed and enable the Global Method Security and the @PostAuthorize annotation.
Enable the @PostAuthorize Annotation
To enable @PreAuthorize and also @PostAuthorize annotations in your Spring Boot application, you will need to first enable the Global Method Security. To enable the Global Method Security, add the @EnableMethodSecurity annotation to any Java class in your application which has the @Configuration annotation. If your application has Spring Security enabled and at least a Basic Authentication configured, then you can add the @EnableMethodSecurity annotation and enable method-level security in a class that configures HTTPSecurity and is annotated with @EnableWebSecurity annotation.
@EnableWebSecurity @EnableMethodSecurity(prePostEnabled = true) public class WebSecurity { @Bean protected SecurityFilterChain configure(HttpSecurity http) throws Exception { ... } }
Notice the use of prePostEnabled=true in the @EnableMethodSecurity annotation. The prePostEnabled=true is what enables the @PreAuthorize and also @PostAuthorize annotations.
Now that you have the @PostAuthorize annotation enabled let’s write a very simple security expression to allow the method to return a return object only when it is allowed.
@PostAuthorize Annotation Example
@PostAuthorize is a Spring security annotation used to specify that a method should be invoked only if the result meets certain criteria after the method execution.
When using the @PostAuthorize annotation, it is very important to keep in mind that this annotation will allow the business logic of a method to execute first and only then the security expression it contains will be evaluated. So, be careful and do not use this annotation with methods that perform modifying queries like, for example, Delete User or Update User.
One of the good use cases using the @PostAuthorize annotation will be a method that reads some information from a database and other sources and returns a return value. For example, we can use the @PostAuthorize annotation with a method that reads user details from a database and then returns a user object from a method.
Let’s write a security expression that allows the method to return user object only if the userId of a user matches the userId of a currently logged in principal user.
The returnObject
The @PostAuthorize annotation has access to an object that the method is going to return. Any object that your method is going to return can be accessed in a security expression via the “returnObject“.
Below is an example of a Java class that contains user details. When writing a security expression for the @PreAuthorize annotation, you can access the userId property of the below class with returnObject.userId.
public class User { private String userId; private String firstName; private String lastName; public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } }
An example of a method-level security expression that accesses return objects userId property.
@PostAuthorize("returnObject.userId == principal.userId")
Let’s have a look at an example of @PreAuthorize annotation used above the method name in a Rest Controller class.
The below method returns an object of User class. A security expression used in the @PostAuthorize annotation will be validated after the method is executed but the method will actually return a value only of the userId of a currently logged in user will match the userId of a User object.
@PostAuthorize("returnObject.userId == principal.userId") @GetMapping(path = "/{id}", produces = { MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE }) public User getUser(@PathVariable String id) { User returnValue = new User(); UserDto userDto = userService.getUserByUserId(id); ModelMapper modelMapper = new ModelMapper(); returnValue = modelMapper.map(userDto, User.class); return returnValue; }
Roles and Authorities
If your application supports user Roles and Authorities, you can write security expressions that validate user authority. For example, the below @PreAuthorize security annotation will allow a method to return a value only if a logged-in user has an ADMIN role or is an owner of the object that is being returned.
@PostAuthorize("hasRole('ADMIN') or returnObject.userId == principal.userId")
Notice the use of a hasRole() method. The hasRole() method is used to check if the currently authenticated user has a specific role.
In the code example above, the expression specifies that the method should only be executed if the authenticated user has the “ADMIN” role, or if the “userId” field of the returned object is equal to the “userId” of the currently authenticated user.
So, if the user has the “ADMIN” role, the method will be executed regardless of the value of the “userId” field. However, if the user doesn’t have the “ADMIN” role, the method will only be executed if the “userId” field of the returned object matches the “userId” of the authenticated user.
Conclusion
I hope this tutorial was of some value to you.
If you are interested to learn how other security annotations work, then have a look at the following tutorials:
Happy learning! 🙋🏻♂️