Spring boot method security
In this article, we will understand 3 different ways to secure methods in spring boot security. Securing methods means they will be invoked only if configured conditions are met.
To enable security over method level in spring boot, first step is to apply
@EnableGlobalMethodSecurity
annotation. This annotation has following 3 options or attributes: 1. prePostEnabled
This should be set to true
to enable @PreAuthorize
and @PostAuthorize
annotations. It is false
by default.
2. secureEnabled
This should be set to true
to enable @Secured
annotation. It is false
by default.
3. jsr250Enabled
This should be set to true
to enable @RolesAllowed
annotation. It is false
by default.
If any of these annotations is used without setting the corresponding attribute to true
, then Spring boot will throw below error
java.lang.IllegalStateException: In the composition of all global method configuration, no annotation support was actually activated
All these annotations will be discussed in detail next.
@EnableGlobalMethodSecurity
can be applied over any configuration class or main class of Spring boot application.
@Secured
This annotation secures a method based on roles. It can take a single or multiple role names as its values.
Method annotated with @Secured
will be invoked only if logged user has role given on @Secured
. Example,
@Secured("ROLE_MANAGER") public List getLoans() { // logic }
This is same as below
@Secured("{ROLE_MANAGER}")
To specify multiple roles, supply them as a string array as below
@Secured({"ROLE_MANAGER", "ROLE_DIRECTOR"})
A user with either of these roles can invoke the method having @Secured
annotation.
A user with any other role will get 403, Forbidden error when accessing this method.
Remember that @Secured
can be applied to a method at any level, that is, at Controller, Service, Repository or any other Spring managed bean.
As the name suggests, this annotation checks if a certain role is allowed to invoke a method. It may accept a single role or multiple roles. Example,
@RolesAllowed("ROLE_MANAGER") public List getLoans() { // logic }
Below are its other usage examples
// single role as array @RolesAllowed({"ROLE_MANAGER"}) // multiple roles @RolesAllowed({"ROLE_MANAGER", "ROLE_DIRECTOR"}) // with value attribute @RolesAllowed(value = {"ROLE_MANAGER"})
@RolesAllowed
may be applied over a method or a class. If applied over a class, then it is applicable to all its methods.
This annotation is similar @Secured
annotation except that it belongs to JSR-250(Java Service Request).
@PreAuthorize
This annotation supports Spring Expression Language(SpEL) and it accepts an expression as argument.
Before invoking a method, this expression is evaluated. If the expression returns true, then the method is invoked else not.
Below is an example of using @PreAuthorize
to invoke a method by a user having ADMIN role.
@PreAuthorize("hasRole('ROLE_ADMIN')") public List getLoans() { // logic }
Only if hasRole()
returns true
, the method is invoked.
Other methods that can be used with @PreAuthorize
are
1. hasAnyRole()
Accepts an array of role names. Allows method invocation if logged user has any of these roles.
Similar to @Secured({"ROLE_ADMIN", "ROLE_OWNER"})
or @RolesAllowed({"ROLE_ADMIN", "ROLE_OWNER"})
2. hasAuthority()
Similar to hasRole()
. It accepts user authority instead of role.
Checks if logged user has supplied authority.
3. hasAnyAuthority()
Checks if logged user has any of supplied authority.
4. permitAll()
Permits method invocation if the value supplied to permitAll
is true
. Example,
@PreAuthorize("permitAll = true")
In place of true
, you must provide an expression with a return value of true
or false
.
5. denyAll()
Opposite to permitAll()
. It will not allow method invocation if the expression supplied to it returns true
.
In addition to above options, @PreAuthorize
also allows us to use method arguments in its conditions. Method arguments should be preceded with # as shown below.
@PreAuthorize("#user == authentication.principal.username") public List getLoans(String user) { // logic }
This code compares the currently logged in user name with the value passed to method argument.
As you can see, @PreAuthorize
is much powerful as compared to @Secured
and @RolesAllowed
in that it can be used to evaluate dynamic expressions as well.
@PostAuthorize
annotation is similar to @PreAuthorize
as it accepts all methods and expressions discussed above. So, below usage of @PostAuthorize
are valid 1. @PostAuthorize("hasRole("'ROLE_ADMIN')") public List getLoans() { // logic } 2. @PostAuthorize("hasAnyRole("{'ROLE_ADMIN', 'ROLE_OWNER'})") public List getLoans() { // logic } 3. @PostAuthorize("#user == authentication.principal.username") public List getLoans() { // logic }
The only difference is that @PostAuthorize
will allow the method to be invoked and then evaluate the expression.
If the expression results in false value, it will return 403, Forbidden status.
One scenario where this could be helpful is that you want to compare an object’s value and return result if it matches with some value.
@PostAuthorize
provides access to a method’s return value with its returnObject
variable. It can be used to return response as shown below
@PostAuthorize("returnObject.name == authentication.principal.username") public User getLoggedInUser(int id) { // fetch user }
Above method will return success response if the name of user matches with the logged in user.