Spring Method Security

In this article, we will look at the Spring method security. The method level Spring security allows us to add security to individual methods within our service layer.

Spring Method Security

In simple terms, Spring method security allows us to support / add authorization supports at the method level. On a high level, we can configure which roles are allowed to access what method within the same service class. Let’s take an example of CustomerService class.

  • A customer service can only use the view method.
  • We only allow the user with Admin permission to call the delete method in the same service class.

In this article, we will look at the steps and configuration to enable spring method level security using the different annotations. Spring security supports both JSR-250 based annotation and Spring security based annotation, which allows us to use the new and powerful Spring expression language.

1. Maven Dependencies

To start, we need to ensure that spring security will be added as a required dependency in our application. For Spring Boot based application, we need to add the spring security starter as dependency on our application.This is how our pom.xml will look like:

<dependencies>
   <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-security</artifactId>
   </dependency>
</dependencies>

This article is part of our Spring security tutorials. You can download the complete source code from our GitHub repository.

2. EnableGlobalMethodSecurity

To enable annotation based security, we need to add the @EnableGlobalMethodSecurity annotation on any @Configuration class.This is how our configuration class will look like:

package com.javadevjournal.config;

import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;

/**
 * EnableGlobalMethodSecurity to allow method level Spring security annotation for our application
 */

@EnableGlobalMethodSecurity(
        securedEnabled = true,
        jsr250Enabled = true,
        prePostEnabled = true
)
public class MethodSecurityConfig {
    //default configuration class
}

Let’s look at few important parameters of the @EnableGlobalMethodSecurity annotation

  • securedEnabled – Determine if the @Security annotation should be enabled.
  • jsr250Enabled – Allow us to use JSR250 based annotation (e.g. @RoleAllowed).
  • prePostEnabled – Enable Spring’s pre/post annotations.

3. Using Spring Method Security

We have configured and enabled the Spring method security configuration. Let’s see how to use them in your code. We will start with the framework’s original @Secured annotation.

3.1. Using @Secured Annotation

The @Secured annotation is used to specify the list of roles on a method. This allows us to provide access to a specific method in case the user has a role. Let’s take the example of a REST API, where we want to give access to an endpoint only in case client has a given role. We can easily do this using the @Secured annotation. Let’s look at the following example:

@RestController
@RequestMapping("/api/v2/users")
public class UserRestAPI {

    @GetMapping("/hello")
    public String getData(){
        return "hello";
    }

    @Secured("ROLE_CUSTOMER")
    @GetMapping("/user/{id}")
    public String getUserById(@PathVariable String id){
        return "id";
    }
}

Here, the first method is accessible to all users, but the second method is only accessible in case the customer have “ROLE_CUSTOMER” role. Spring Security passes this information to the AccessDecisionManager to make the actual decision. If you run this and try to access the second method as anonymous user, you will get access denied error back from the API. We can also pass a list of roles to the @Secured annotation.

@Secured({"ROLE_CUSTOMER", "ROLE_USER"})
@GetMapping("/user/{id}")
public String getUserById(@PathVariable String id){
  return "id";
}

We can’t use Spring EL on the @Secured annotation. Use the Expression based access control to use Spring EL on the authorization mechanism.

3.2. Using @PreAuthorize and @PostAuthorize Annotations

Both @PreAuthrize and @PostAuthorize annotations allow us to use the Spring expression based syntax to activate Spring method security. To use these annotations, we need to set the prePostEnabled = true in the @EnableGlobalMethodSecurity annotation. As name suggests:

  1. The @PreAuthorize annotation check before method execution.
  2. @PostAuthorize check if it can alter the result.

Let’s see how to change the above security declaration to use the new annotation

@RestController
@RequestMapping("/api/v2/users")
public class UserRestAPI {

    @GetMapping("/hello")
    public String getData() {
        return "hello";
    }

    //@Secured({"ROLE_CUSTOMER", "ROLE_USER"})  this is ole configuration
    @PreAuthorize("hasRole('ROLE_CUSTOMER') or hasRole('ROLE_USER')")
    @GetMapping("/user/{id}")
    public String getUserById(@PathVariable String id) {
        return "id";
    }
}

As we said, you can use the Spring expression with these annotations, so if we want we can use the method arguments as part of the Spring security expression.

@RestController
@RequestMapping("/api/v2/users")
public class UserRestAPI {

    @GetMapping("/hello")
    public String getData() {
        return "hello";
    }

    //@Secured({"ROLE_CUSTOMER", "ROLE_USER"})  this is ole configuration
    @PreAuthorize(@PreAuthorize("#username == authentication.principal.username"))
    @GetMapping("/user/{id}")
    public String getUserById(@PathVariable String id) {
        return "id";
    }
}

The @PostAuthorize work i the same way except it can change the after result output.

Spring Method Security

3.3. Multiple Spring Security Annotations on a Method

In case you can’t achieve the desired results with single security annotation, you can always combine multiple Spring security annotations

 @Secured({
     "ROLE_CUSTOMER",
     "ROLE_USER"
 })
 @PreAuthorize(@PreAuthorize("#username == authentication.principal.username"))
 @GetMapping("/user/{id}")
 public String getUserById(@PathVariable String id) {
     return "id";
 }

Keep in mind that Spring Security’s annotations are not repeatable, so you can’t have two instances of @PreAuthorize etc., but you can combine @PreAuthorize, @Secured, and JSR-250 annotations.

4. Spring Security Class Level Annotation

Spring security provide option to use these annotations at the class level. This is really useful if we are using the same type of annotation for each method within the class. Here, we can add the annotation at the class level to protect the entire class based on the same rule and authorization workflow.

@Service("customerAccountService")
@PreAuthorize("hasRole('ROLE_CUSTOMER')")
public class DefaultCustomerAccountService implements CustomerAccountService {
    
}

Spring security uses AOP internally for method level security. Global method security is used to apply security checks to certain methods. Look at the GlobalMethodSecurityBeanDefinitionParser class for more information.

Summary

In this article, we look at the Spring method security. We saw the different option to apply the Spring security on the method level. We covered the following items.

  1. How to enable Spring method security.
  2. How can we use different Spring security annotations on the method level?
  3. We learned the option to use the security annotation on the method level.