Spring Security configuration module providing comprehensive declarative security configuration capabilities for Spring applications
—
Spring Security Config provides comprehensive method-level security configuration through annotations and configuration classes. Method security enables fine-grained access control at the method level using annotations like @PreAuthorize, @PostAuthorize, @Secured, and JSR-250 annotations.
Modern method-level security configuration annotation (Spring Security 5.6+).
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(MethodSecurityConfiguration.class)
public @interface EnableMethodSecurity {
/**
* Determines if Spring Security's pre/post annotations should be enabled.
* @return true if pre/post annotations are enabled, false otherwise. Default is true.
*/
boolean prePostEnabled() default true;
/**
* Determines if Spring Security's @Secured annotation should be enabled.
* @return true if @Secured is enabled, false otherwise. Default is false.
*/
boolean securedEnabled() default false;
/**
* Determines if JSR-250 annotations should be enabled.
* @return true if JSR-250 annotations are enabled, false otherwise. Default is false.
*/
boolean jsr250Enabled() default false;
/**
* Indicate whether subclass-based (CGLIB) proxies are to be created as opposed
* to standard Java interface-based proxies.
* @return true to use CGLIB proxies, false for JDK proxies. Default is false.
*/
boolean proxyTargetClass() default false;
/**
* Indicate how security advice should be applied.
* @return the advice mode. Default is PROXY.
*/
AdviceMode mode() default AdviceMode.PROXY;
/**
* Indicate the offset in the order for SecurityMethodInterceptor.
* @return the offset value. Default is 0.
*/
int offset() default 0;
}Usage Example:
@Configuration
@EnableMethodSecurity(
prePostEnabled = true,
securedEnabled = true,
jsr250Enabled = true
)
public class MethodSecurityConfig {
@Bean
public MethodSecurityExpressionHandler methodSecurityExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler =
new DefaultMethodSecurityExpressionHandler();
expressionHandler.setPermissionEvaluator(customPermissionEvaluator());
return expressionHandler;
}
}Legacy method-level security configuration annotation.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(GlobalMethodSecurityConfiguration.class)
@Deprecated
public @interface EnableGlobalMethodSecurity {
boolean prePostEnabled() default false;
boolean securedEnabled() default false;
boolean jsr250Enabled() default false;
boolean proxyTargetClass() default false;
AdviceMode mode() default AdviceMode.PROXY;
int order() default Ordered.LOWEST_PRECEDENCE;
}Reactive method security configuration for WebFlux applications.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(ReactiveMethodSecurityConfiguration.class)
public @interface EnableReactiveMethodSecurity {
/**
* Indicate whether subclass-based (CGLIB) proxies are to be created as opposed
* to standard Java interface-based proxies.
* @return true to use CGLIB proxies, false for JDK proxies. Default is false.
*/
boolean proxyTargetClass() default false;
/**
* Indicate how security advice should be applied.
* @return the advice mode. Default is PROXY.
*/
AdviceMode mode() default AdviceMode.PROXY;
/**
* Indicate the order in which the SecurityMethodInterceptor should be applied.
* @return the order value. Default is LOWEST_PRECEDENCE.
*/
int order() default Ordered.LOWEST_PRECEDENCE;
/**
* Indicate whether to use AuthorizationManager for reactive method security.
* @return true to use AuthorizationManager, false for legacy approach. Default is true.
*/
boolean useAuthorizationManager() default true;
}Base class for method security configuration (legacy approach).
public abstract class GlobalMethodSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
// Configuration Methods
protected void configure(AuthenticationManagerBuilder auth) throws Exception {}
// Access Decision Manager
protected AccessDecisionManager accessDecisionManager() {
List<AccessDecisionVoter<?>> decisionVoters = new ArrayList<>();
ExpressionBasedPreInvocationAdvice expressionAdvice =
new ExpressionBasedPreInvocationAdvice();
expressionAdvice.setExpressionHandler(getExpressionHandler());
if (prePostEnabled()) {
decisionVoters.add(new PreInvocationAuthorizationAdviceVoter(expressionAdvice));
}
if (jsr250Enabled()) {
decisionVoters.add(new Jsr250Voter());
}
return new AffirmativeBased(decisionVoters);
}
// Expression Handler
protected MethodSecurityExpressionHandler createExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler =
new DefaultMethodSecurityExpressionHandler();
if (authenticationTrustResolver != null) {
expressionHandler.setTrustResolver(authenticationTrustResolver);
}
if (roleHierarchy != null) {
expressionHandler.setRoleHierarchy(roleHierarchy);
}
if (permissionEvaluator != null) {
expressionHandler.setPermissionEvaluator(permissionEvaluator);
}
return expressionHandler;
}
// Configuration Properties
protected boolean prePostEnabled() { return false; }
protected boolean securedEnabled() { return false; }
protected boolean jsr250Enabled() { return false; }
// Authentication Manager
protected AuthenticationManager authenticationManager() throws Exception {
return authenticationManagerBuilder.build();
}
// Run As Manager
protected RunAsManager runAsManager() { return null; }
// Permission Evaluator
protected PermissionEvaluator permissionEvaluator() { return null; }
// Role Hierarchy
protected RoleHierarchy roleHierarchy() { return null; }
// Authentication Trust Resolver
protected AuthenticationTrustResolver authenticationTrustResolver() { return null; }
}Usage Example:
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class CustomMethodSecurityConfig extends GlobalMethodSecurityConfiguration {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailsService())
.passwordEncoder(passwordEncoder());
}
@Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler =
new DefaultMethodSecurityExpressionHandler();
expressionHandler.setPermissionEvaluator(customPermissionEvaluator());
expressionHandler.setRoleHierarchy(roleHierarchy());
return expressionHandler;
}
@Override
protected AccessDecisionManager accessDecisionManager() {
List<AccessDecisionVoter<?>> decisionVoters = Arrays.asList(
new RoleVoter(),
new AuthenticatedVoter(),
new WebExpressionVoter()
);
return new AffirmativeBased(decisionVoters);
}
@Bean
public RoleHierarchy roleHierarchy() {
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER > ROLE_GUEST");
return roleHierarchy;
}
@Bean
public PermissionEvaluator customPermissionEvaluator() {
return new CustomPermissionEvaluator();
}
}Configuration class for reactive method security.
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ReactiveMethodSecurityConfiguration {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public ReactiveMethodSecurityInterceptor methodSecurityInterceptor(
ReactiveAuthorizationManager<MethodInvocation> authorizationManager) {
ReactiveMethodSecurityInterceptor interceptor =
new ReactiveMethodSecurityInterceptor();
interceptor.setAuthorizationManager(authorizationManager);
return interceptor;
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public ReactiveAuthorizationManager<MethodInvocation> methodAuthorizationManager() {
return new PrePostAdviceReactiveMethodInterceptor();
}
}Registrar for method security metadata source advisors.
public final class MethodSecurityMetadataSourceAdvisorRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
// Register method security advisor
BeanDefinition advisor = BeanDefinitionBuilder
.rootBeanDefinition(MethodSecurityMetadataSourceAdvisor.class)
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE)
.getBeanDefinition();
registry.registerBeanDefinition("methodSecurityAdvisor", advisor);
}
}Configuration for authorization proxies in method security.
@Configuration
@ConditionalOnClass(AuthorizationManager.class)
public class AuthorizationProxyConfiguration {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public AuthorizationAdvisor authorizationAdvisor(
AuthorizationManager<MethodInvocation> authorizationManager) {
return new AuthorizationAdvisor(authorizationManager);
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public AuthorizationManager<MethodInvocation> methodAuthorizationManager() {
return new PrePostAdviceAuthorizationManager();
}
}Interface for handling method security expressions.
public interface MethodSecurityExpressionHandler extends SecurityExpressionHandler<MethodInvocation> {
/**
* Filter the collection or array returned from a method invocation.
* @param filterObject the collection or array to filter
* @param expression the expression to evaluate for each element
* @param ctx the evaluation context
* @return the filtered collection or array
*/
Object filter(Object filterObject, Expression expression, EvaluationContext ctx);
/**
* Create an evaluation context for method invocation.
* @param auth the current authentication
* @param mi the method invocation
* @return the evaluation context
*/
EvaluationContext createEvaluationContext(Authentication auth, MethodInvocation mi);
/**
* Set the permission evaluator for object-level security.
* @param permissionEvaluator the permission evaluator
*/
void setPermissionEvaluator(PermissionEvaluator permissionEvaluator);
/**
* Set the role hierarchy for inherited roles.
* @param roleHierarchy the role hierarchy
*/
void setRoleHierarchy(RoleHierarchy roleHierarchy);
/**
* Set the trust resolver for authentication state evaluation.
* @param trustResolver the authentication trust resolver
*/
void setTrustResolver(AuthenticationTrustResolver trustResolver);
}Default implementation of method security expression handler.
public class DefaultMethodSecurityExpressionHandler extends AbstractSecurityExpressionHandler<MethodInvocation>
implements MethodSecurityExpressionHandler, BeanFactoryAware {
private PermissionEvaluator permissionEvaluator = new DenyAllPermissionEvaluator();
private RoleHierarchy roleHierarchy;
private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
@Override
public EvaluationContext createEvaluationContext(Authentication auth, MethodInvocation mi) {
MethodSecurityEvaluationContext ctx = new MethodSecurityEvaluationContext(auth, mi, getParameterNameDiscoverer());
MethodSecurityExpressionRoot root = new MethodSecurityExpressionRoot(auth);
root.setPermissionEvaluator(permissionEvaluator);
root.setTrustResolver(trustResolver);
root.setRoleHierarchy(roleHierarchy);
ctx.setRootObject(root);
return ctx;
}
@Override
public Object filter(Object filterObject, Expression expression, EvaluationContext ctx) {
if (filterObject instanceof Collection) {
return filterCollection((Collection<?>) filterObject, expression, ctx);
} else if (filterObject.getClass().isArray()) {
return filterArray((Object[]) filterObject, expression, ctx);
}
return filterObject;
}
// Setter methods
public void setPermissionEvaluator(PermissionEvaluator permissionEvaluator) {
this.permissionEvaluator = permissionEvaluator;
}
public void setRoleHierarchy(RoleHierarchy roleHierarchy) {
this.roleHierarchy = roleHierarchy;
}
public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
this.trustResolver = trustResolver;
}
}Security annotations for method-level access control.
// Pre-authorization annotation
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface PreAuthorize {
/**
* Spring Expression Language (SpEL) expression used to determine
* if a method invocation is allowed.
* @return the SpEL expression
*/
String value();
}
// Post-authorization annotation
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface PostAuthorize {
/**
* Spring Expression Language (SpEL) expression used to determine
* if a method return value should be returned to the caller.
* @return the SpEL expression
*/
String value();
}
// Pre-filtering annotation
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface PreFilter {
/**
* Spring Expression Language (SpEL) expression used to filter
* method arguments before method invocation.
* @return the SpEL expression
*/
String value();
/**
* Name of the argument to filter when there are multiple arguments.
* @return the argument name
*/
String filterTarget() default "";
}
// Post-filtering annotation
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface PostFilter {
/**
* Spring Expression Language (SpEL) expression used to filter
* the method return value.
* @return the SpEL expression
*/
String value();
}
// Secured annotation
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Secured {
/**
* List of roles/authorities required to invoke the secured method.
* @return the required roles/authorities
*/
String[] value();
}Usage Examples:
@Service
public class DocumentService {
// Pre-authorization: Check role before method execution
@PreAuthorize("hasRole('ADMIN')")
public void deleteDocument(Long documentId) {
// Implementation
}
// Pre-authorization: Check ownership
@PreAuthorize("@documentService.isOwner(authentication.name, #documentId)")
public Document getDocument(Long documentId) {
// Implementation
}
// Post-authorization: Filter return value based on ownership
@PostAuthorize("returnObject.owner == authentication.name or hasRole('ADMIN')")
public Document loadDocument(Long documentId) {
// Implementation
}
// Pre-filtering: Filter collection arguments
@PreFilter("hasPermission(filterObject, 'READ')")
public List<Document> processDocuments(List<Document> documents) {
// Process only documents the user can read
return documents;
}
// Post-filtering: Filter collection return values
@PostFilter("hasPermission(filterObject, 'READ')")
public List<Document> findAllDocuments() {
// Return only documents the user can read
return documentRepository.findAll();
}
// Secured annotation: Simple role-based access
@Secured({"ROLE_USER", "ROLE_ADMIN"})
public List<Document> getUserDocuments() {
// Implementation
}
// JSR-250 annotations
@RolesAllowed("ADMIN")
public void adminOnlyMethod() {
// Implementation
}
@PermitAll
public String publicMethod() {
return "Public access";
}
@DenyAll
public void restrictedMethod() {
// This method is never accessible
}
}Interface for custom permission evaluation logic.
public interface PermissionEvaluator {
/**
* Determine whether the user has the given permission for the domain object.
* @param authentication the user authentication
* @param targetDomainObject the domain object
* @param permission the permission to check
* @return true if access is granted, false otherwise
*/
boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission);
/**
* Determine whether the user has the given permission for the domain object
* identified by its identifier.
* @param authentication the user authentication
* @param targetId the identifier of the domain object
* @param targetType the type of the domain object
* @param permission the permission to check
* @return true if access is granted, false otherwise
*/
boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission);
}Custom Permission Evaluator Example:
@Component
public class CustomPermissionEvaluator implements PermissionEvaluator {
@Autowired
private PermissionService permissionService;
@Override
public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
if (authentication == null || targetDomainObject == null || !(permission instanceof String)) {
return false;
}
String username = authentication.getName();
String permissionName = (String) permission;
if (targetDomainObject instanceof Document) {
Document document = (Document) targetDomainObject;
return permissionService.hasDocumentPermission(username, document.getId(), permissionName);
}
return false;
}
@Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
if (authentication == null || targetId == null || targetType == null || !(permission instanceof String)) {
return false;
}
String username = authentication.getName();
String permissionName = (String) permission;
if ("Document".equals(targetType)) {
return permissionService.hasDocumentPermission(username, (Long) targetId, permissionName);
}
return false;
}
}
// Usage in service methods
@Service
public class DocumentService {
@PreAuthorize("hasPermission(#documentId, 'Document', 'READ')")
public Document getDocument(Long documentId) {
return documentRepository.findById(documentId);
}
@PreAuthorize("hasPermission(#document, 'WRITE')")
public Document updateDocument(Document document) {
return documentRepository.save(document);
}
}Apply security annotations at the class level for all methods:
@Service
@PreAuthorize("hasRole('USER')")
public class UserService {
// All methods require USER role by default
public List<User> getAllUsers() {
return userRepository.findAll();
}
// Override class-level security
@PreAuthorize("hasRole('ADMIN')")
public void deleteUser(Long userId) {
userRepository.deleteById(userId);
}
// No additional security beyond class level
public User getCurrentUser(Authentication auth) {
return userRepository.findByUsername(auth.getName());
}
}Use complex SpEL expressions for sophisticated authorization logic:
@Service
public class ProjectService {
@PreAuthorize("hasRole('ADMIN') or " +
"(hasRole('PROJECT_MANAGER') and @projectService.isProjectManager(authentication.name, #projectId)) or " +
"(hasRole('DEVELOPER') and @projectService.isProjectMember(authentication.name, #projectId))")
public Project getProject(Long projectId) {
return projectRepository.findById(projectId);
}
@PreAuthorize("@projectService.canEditProject(authentication, #project)")
public Project updateProject(Project project) {
return projectRepository.save(project);
}
public boolean canEditProject(Authentication auth, Project project) {
String username = auth.getName();
return hasRole(auth, "ADMIN") ||
(hasRole(auth, "PROJECT_MANAGER") && isProjectManager(username, project.getId())) ||
(hasRole(auth, "DEVELOPER") && isProjectLead(username, project.getId()));
}
}Method security for reactive applications:
@Service
public class ReactiveDocumentService {
@PreAuthorize("hasRole('USER')")
public Mono<Document> getDocument(String documentId) {
return documentRepository.findById(documentId);
}
@PreAuthorize("hasPermission(#document, 'WRITE')")
public Mono<Document> saveDocument(Document document) {
return documentRepository.save(document);
}
@PostFilter("hasPermission(filterObject, 'READ')")
public Flux<Document> findAllDocuments() {
return documentRepository.findAll();
}
}Install with Tessl CLI
npx tessl i tessl/maven-org-springframework-security--spring-security-config