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 thedelete
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:
- The
@PreAuthorize
annotation check before method execution. @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.
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.
- How to enable Spring method security.
- How can we use different Spring security annotations on the method level?
- We learned the option to use the security annotation on the method level.