Method-level authorization using annotations on service methods with support for SpEL expressions for complex authorization logic.
Enable method-level security using @EnableMethodSecurity.
package org.springframework.security.config.annotation.method.configuration;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import({PrePostMethodSecurityConfiguration.class})
public @interface EnableMethodSecurity {
/**
* Enable @PreAuthorize/@PostAuthorize annotations (default: true)
*/
boolean prePostEnabled() default true;
/**
* Enable @Secured annotation (default: false)
*/
boolean securedEnabled() default false;
/**
* Enable JSR-250 annotations (@RolesAllowed, @PermitAll, @DenyAll) (default: false)
*/
boolean jsr250Enabled() default false;
/**
* Use CGLIB proxies instead of JDK dynamic proxies (default: false)
*/
boolean proxyTargetClass() default false;
/**
* Advice mode (PROXY or ASPECTJ) (default: PROXY)
*/
AdviceMode mode() default AdviceMode.PROXY;
/**
* Authorization check offset (default: 0)
*/
int offset() default 0;
}Usage:
@Configuration
@EnableMethodSecurity(
prePostEnabled = true,
securedEnabled = false,
jsr250Enabled = false
)
public class MethodSecurityConfig {
}Migration Note: @EnableGlobalMethodSecurity is deprecated as of Spring Security 5.6. Use @EnableMethodSecurity instead, which provides the same functionality with better defaults and improved performance.
// Deprecated (Spring Security 5.5 and earlier)
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
// Current (Spring Security 5.6+)
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true)Enable method-level security for reactive (WebFlux) applications.
package org.springframework.security.config.annotation.method.configuration;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(ReactiveMethodSecuritySelector.class)
public @interface EnableReactiveMethodSecurity {
/**
* Use CGLIB proxies instead of JDK dynamic proxies (default: false)
*/
boolean proxyTargetClass() default false;
/**
* Advice mode (PROXY or ASPECTJ) (default: PROXY)
*/
AdviceMode mode() default AdviceMode.PROXY;
/**
* Ordering for security advisor (default: LOWEST_PRECEDENCE)
*/
int order() default Ordered.LOWEST_PRECEDENCE;
/**
* Use ReactiveAuthorizationManager-based Method Security (default: true)
* @since 5.8
*/
boolean useAuthorizationManager() default true;
}Usage:
@Configuration
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class ReactiveMethodSecurityConfig {
}
// Use the same annotations on reactive methods
@Service
public class ReactiveSecureService {
@PreAuthorize("hasRole('ADMIN')")
public Mono<String> adminOnlyMethod() {
return Mono.just("admin data");
}
@PreAuthorize("hasAuthority('READ_PRIVILEGE')")
public Flux<String> readData() {
return Flux.just("data1", "data2", "data3");
}
@PostAuthorize("returnObject.owner == authentication.name")
public Mono<Document> getDocument(Long id) {
return documentRepository.findById(id);
}
}Check authorization before method execution.
package org.springframework.security.access.prepost;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface PreAuthorize {
/**
* SpEL expression for authorization check
*/
String value();
}Usage Examples:
@Service
public class SecureService {
// Role-based access
@PreAuthorize("hasRole('ADMIN')")
public void adminOnlyMethod() {
// Only accessible by users with ADMIN role
}
// Multiple roles
@PreAuthorize("hasAnyRole('USER', 'ADMIN')")
public void userOrAdminMethod() {
// Accessible by USER or ADMIN
}
// Authority-based access
@PreAuthorize("hasAuthority('READ_PRIVILEGE')")
public String readData() {
return "sensitive data";
}
// Multiple authorities
@PreAuthorize("hasAnyAuthority('READ', 'WRITE', 'ADMIN')")
public void multipleAuthorities() {
}
// Method parameter access
@PreAuthorize("#username == authentication.name")
public void updateUser(String username, User user) {
// Only the user themselves can update
}
// Object property access
@PreAuthorize("#user.id == authentication.principal.id")
public void updateUserDetails(User user) {
// Check user ID matches authenticated user
}
// Complex expressions
@PreAuthorize("hasRole('ADMIN') or #id == authentication.principal.id")
public User getUser(Long id) {
// Admin can access any user, others only themselves
}
// Authentication check
@PreAuthorize("isAuthenticated()")
public void authenticatedOnlyMethod() {
}
// Anonymous check
@PreAuthorize("isAnonymous()")
public void anonymousOnlyMethod() {
}
// Fully authenticated (not remember-me)
@PreAuthorize("isFullyAuthenticated()")
public void fullyAuthenticatedMethod() {
}
// IP address check
@PreAuthorize("hasIpAddress('192.168.1.0/24')")
public void internalNetworkMethod() {
}
// Custom security expression
@PreAuthorize("@customSecurityService.check(authentication, #id)")
public void customSecurityCheck(Long id) {
}
}
// Custom security service
@Service("customSecurityService")
public class CustomSecurityService {
public boolean check(Authentication authentication, Long id) {
// Custom authorization logic
return true;
}
}Check authorization after method execution, allowing access to return value.
package org.springframework.security.access.prepost;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface PostAuthorize {
/**
* SpEL expression for authorization check
*/
String value();
}Usage Examples:
@Service
public class DocumentService {
// Check return value property
@PostAuthorize("returnObject.owner == authentication.name")
public Document getDocument(Long id) {
return documentRepository.findById(id);
}
// Access return value attributes
@PostAuthorize("returnObject != null and returnObject.public == true or hasRole('ADMIN')")
public Post getPost(Long id) {
return postRepository.findById(id);
}
// Complex return value checks
@PostAuthorize("@documentPermissionEvaluator.canRead(authentication, returnObject)")
public Document getSecureDocument(Long id) {
return documentRepository.findById(id);
}
}Filter collection/array method parameters before execution.
package org.springframework.security.access.prepost;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface PreFilter {
/**
* SpEL expression for filtering
*/
String value();
/**
* Parameter name to filter (required if multiple collection parameters)
*/
String filterTarget() default "";
}Usage Examples:
@Service
public class DataService {
// Filter list before processing
@PreFilter("filterObject.owner == authentication.name")
public void updateDocuments(List<Document> documents) {
// Only documents owned by current user will be processed
documentRepository.saveAll(documents);
}
// Filter array
@PreFilter("filterObject != null and filterObject.active == true")
public void processItems(Item[] items) {
// Only active items will be processed
}
// Specify filter target when multiple collections
@PreFilter(value = "filterObject.approved == true", filterTarget = "items")
public void processMultiple(List<Item> items, Set<Category> categories) {
// Only items collection will be filtered
}
}Filter collection/array return values after execution.
package org.springframework.security.access.prepost;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface PostFilter {
/**
* SpEL expression for filtering
*/
String value();
}Usage Examples:
@Service
public class DocumentService {
// Filter returned list
@PostFilter("filterObject.owner == authentication.name or hasRole('ADMIN')")
public List<Document> getAllDocuments() {
// Return only documents owned by user (unless ADMIN)
return documentRepository.findAll();
}
// Filter by permission
@PostFilter("@documentPermissionEvaluator.canRead(authentication, filterObject)")
public List<Document> getAccessibleDocuments() {
return documentRepository.findAll();
}
// Complex filtering
@PostFilter("filterObject.public == true or filterObject.sharedWith.contains(authentication.name)")
public List<Post> getPosts() {
return postRepository.findAll();
}
}Role-based authorization using @Secured (requires securedEnabled = true).
package org.springframework.security.access.annotation;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Secured {
/**
* List of roles (with ROLE_ prefix)
*/
String[] value();
}Usage Examples:
@Service
public class AdminService {
@Secured("ROLE_ADMIN")
public void adminOnlyMethod() {
}
@Secured({"ROLE_USER", "ROLE_ADMIN"})
public void userOrAdmin() {
}
}Standard Java security annotations (requires jsr250Enabled = true).
package jakarta.annotation.security;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RolesAllowed {
String[] value();
}
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PermitAll {
}
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DenyAll {
}Usage Examples:
@Service
public class JsrSecureService {
@RolesAllowed("ADMIN")
public void adminOnly() {
}
@RolesAllowed({"USER", "ADMIN"})
public void userOrAdmin() {
}
@PermitAll
public void publicMethod() {
}
@DenyAll
public void neverAccessible() {
}
}Enable method security for reactive applications.
package org.springframework.security.config.annotation.method.configuration;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import({ReactiveAuthorizationManagerMethodSecurityConfiguration.class})
public @interface EnableReactiveMethodSecurity {
boolean proxyTargetClass() default false;
AdviceMode mode() default AdviceMode.PROXY;
int order() default Ordered.LOWEST_PRECEDENCE;
boolean useAuthorizationManager() default true;
}Usage:
@Configuration
@EnableReactiveMethodSecurity
public class ReactiveMethodSecurityConfig {
}
@Service
public class ReactiveSecureService {
@PreAuthorize("hasRole('ADMIN')")
public Mono<String> getSecretData() {
return Mono.just("secret");
}
@PostAuthorize("returnObject.owner == authentication.name")
public Mono<Document> getDocument(Long id) {
return documentRepository.findById(id);
}
@PostFilter("filterObject.public == true or filterObject.owner == authentication.name")
public Flux<Post> getPosts() {
return postRepository.findAll();
}
}Common SpEL expressions for method security:
| Expression | Description |
|---|---|
hasRole('ROLE') | True if user has specified role (without ROLE_ prefix) |
hasAnyRole('ROLE1', 'ROLE2') | True if user has any of the specified roles |
hasAuthority('AUTHORITY') | True if user has specified authority |
hasAnyAuthority('AUTH1', 'AUTH2') | True if user has any of the specified authorities |
principal | Current user principal object |
authentication | Current Authentication object |
isAuthenticated() | True if user is authenticated |
isAnonymous() | True if user is anonymous |
isFullyAuthenticated() | True if user is fully authenticated (not remember-me) |
isRememberMe() | True if authenticated via remember-me |
hasIpAddress('IP') | True if request comes from specified IP address |
#paramName | Access method parameter by name |
returnObject | Access method return value (in @PostAuthorize/@PostFilter) |
filterObject | Current element in filtering operations |
@beanName.method() | Call method on Spring bean |
Apply security annotations at class level:
@Service
@PreAuthorize("hasRole('ADMIN')")
public class AdminService {
// All methods require ADMIN role by default
public void method1() {
}
public void method2() {
}
// Override class-level annotation
@PreAuthorize("hasRole('SUPER_ADMIN')")
public void superAdminMethod() {
}
// Override to allow all
@PreAuthorize("permitAll()")
public void publicMethod() {
}
}Create custom security expressions:
@Component("customSecurity")
public class CustomSecurityExpressionService {
public boolean hasPermission(Authentication authentication, Object targetObject, String permission) {
// Custom permission check logic
return permissionEvaluator.hasPermission(authentication, targetObject, permission);
}
public boolean isOwner(Authentication authentication, Long resourceId) {
String username = authentication.getName();
Resource resource = resourceRepository.findById(resourceId);
return resource != null && resource.getOwner().equals(username);
}
public boolean canAccessDepartment(Authentication authentication, String departmentCode) {
UserDetails user = (UserDetails) authentication.getPrincipal();
return user.getDepartments().contains(departmentCode);
}
}
// Usage
@Service
public class ResourceService {
@PreAuthorize("@customSecurity.isOwner(authentication, #id)")
public void updateResource(Long id, Resource resource) {
}
@PreAuthorize("@customSecurity.canAccessDepartment(authentication, #deptCode)")
public List<Employee> getEmployees(String deptCode) {
}
}