Spring Security Method Level Annotations Example
This tutorial demonstrates how to use Spring Security Method Level Annotations. We can use Spring Security to secure our service layer. We can restrict which roles are able to execute a method by annotating the method with any of spring security annotations or the standard java JSR-250
annotaitons.
Maven Dependencies
We use Apache Maven to manage our project dependencies. Make sure the following dependencies reside on the class-path.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.memorynotfound.spring.security</groupId>
<artifactId>method-level-security</artifactId>
<version>1.0.0-SNAPSHOT</version>
<url>http://memorynotfound.com</url>
<name>Spring Security - ${project.artifactId}</name>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.8.RELEASE</version>
</parent>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>jsr250-api</artifactId>
<version>1.0</version>
</dependency>
<!-- testing -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Enable Method Level Security
By annotating the class with @EnableGlobalMethodSecurity
, we can enable method level security using annotations. We can optionally configure which annotations we’ll allow. You can enable one of the following.
securedEnabled
– enables the spring@Secured
annotation.jsr250Enabled
– enables theJSR-250
standard java security annotations.prePostEnabled
– enables the spring@PreAuthorize
andPostAuthorize
annotations.
package com.memorynotfound.spring.security.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableGlobalMethodSecurity(
securedEnabled = true,
jsr250Enabled = true,
prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("password").roles("USER")
.and()
.withUser("admin").password("password").roles("ADMIN");
}
}
The equivalent Spring Security XML Configuration spring-security-config.xml
is located in the src/main/resources
folder.
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<global-method-security
secured-annotations="enabled"
jsr250-annotations="enabled"
pre-post-annotations="enabled"/>
<http>
<http-basic/>
</http>
<authentication-manager>
<authentication-provider>
<user-service>
<user name="user"
password="password"
authorities="ROLE_USER" />
<user name="manager"
password="password"
authorities="ROLE_MANAGER" />
</user-service>
</authentication-provider>
</authentication-manager>
</beans:beans>
Spring Security Secured Annotations
Adding an annotation to a method (on a class or interface) limits the access to that method accordingly. Here we used the @Secured
annotation.
package com.memorynotfound.spring.security.web;
import org.springframework.security.access.annotation.Secured;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("book")
public class BookController {
@GetMapping("anonymous")
@Secured("ROLE_ANONYMOUS")
public String anonymously() {
return "Hello, World!";
}
@GetMapping("has-role")
@Secured("ROLE_ADMIN")
public String hasRole() {
return "Hello, World!";
}
}
Spring Security JSR-250 Annotations
Adding an annotation to a method (on a class or interface) limits the access to that method accordingly. Here we used the @PermitAll
and @RolesAllowed
annotations.
package com.memorynotfound.spring.security.web;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("anonymous")
@PermitAll
public String anonymously() {
return "Hello, World!";
}
@GetMapping("has-role")
@RolesAllowed({"ROLE_ADMIN"})
public String hasRole() {
return "Hello, World!";
}
}
Spring Security Pre Post Annotations
Adding an annotation to a method (on a class or interface) limits the access to that method accordingly. Here we used the @PreAuthorize
annotation.
package com.memorynotfound.spring.security.web;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("bank")
public class BankController {
@GetMapping("anonymous")
@PreAuthorize("permitAll()")
public String anonymously() {
return "Hello, World!";
}
@GetMapping("has-role")
@PreAuthorize("hasAnyAuthority('ROLE_ADMIN')")
public String hasRole() {
return "Hello, World!";
}
}
Demo
Access http://localhost:8080/bank/has-role
with user: user.
Access http://localhost:8080/bank/anonymous
with user: user.
Spring Security Method Level Annotations Integration Test
We can use MockMvc
to write some spring integration tests.
package com.memorynotfound.spring.security.test;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest
@AutoConfigureMockMvc
@RunWith(SpringJUnit4ClassRunner.class)
public class BasicAuthenticationIntegrationTests {
@Autowired
private MockMvc mockMvc;
@Test
public void accessAnonymous() throws Exception {
this.mockMvc.perform(
get("/bank/anonymous")
.with(httpBasic("user", "password")))
.andExpect(
status().isOk());
}
@Test
public void accessRoleProtected() throws Exception {
this.mockMvc.perform(
get("/bank/has-role")
.with(httpBasic("user", "password")))
.andExpect(
status().is4xxClientError());
}
}
Spring Security Integration Test Results
References
- Spring Security Documentation
- Spring Security Method Documentation
- @Secured JavaDoc
- @PermitAll JavaDoc
- @PreAuthorize JavaDoc
- @PostAuthorize JavaDoc
- @EnableGlobalMethodSecurity JavaDoc