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.

Enabling Method Security
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.

@RolesAllowed
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
@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.

That is all on different methods to apply method level security in spring boot security application. Hope the article was useful.