如何自定义Spring Security的@PreAuthorize注解实现权限校验
解决Spring Security自定义权限注解的SPEL属性传递问题
正确实现自定义权限注解
你遇到的核心问题是SPEL表达式无法直接引用自定义注解的value属性——默认上下文没有暴露该变量。正确的做法是通过SPEL的@annotation语法获取当前方法上的自定义注解实例,再提取其value属性。
修改你的AuthorityRequired注解如下:
import org.springframework.security.access.prepost.PreAuthorize; import java.lang.annotation.*; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) // 通过@annotation获取当前方法的AuthorityRequired注解实例,再取其value属性 @PreAuthorize("@tenantSecurityService.hasAuthority(@annotation(com.yourpackage.AuthorityRequired).value)") public @interface AuthorityRequired { String value(); }
关键说明
@annotation(com.yourpackage.AuthorityRequired)会告诉SPEL解析器去当前被注解的方法上查找AuthorityRequired注解实例- 直接通过
.value访问该注解的属性,传递给你的tenantSecurityService.hasAuthority()方法
这样你就可以像期望的那样使用注解:
@PostMapping @AuthorityRequired(Authorities.PRODUCTS_WRITE) public Product createProduct(@RequestBody @Valid ProductCreateRequest createRequest) { return productService.createProduct(createRequest); }
其他便捷的权限实现思路
如果需要更灵活的权限控制,还可以考虑以下方案:
1. 扩展Spring Security表达式根
自定义SecurityExpressionRoot,添加自定义的权限校验方法,这样可以直接在@PreAuthorize中使用更简洁的表达式:
public class CustomSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations { private Object filterObject; private Object returnObject; private TenantSecurityService tenantSecurityService; public CustomSecurityExpressionRoot(Authentication authentication) { super(authentication); } // 自定义方法,直接接收权限参数 public boolean hasTenantAuthority(String authority) { return tenantSecurityService.hasAuthority(authority); } // 注入自定义服务 public void setTenantSecurityService(TenantSecurityService tenantSecurityService) { this.tenantSecurityService = tenantSecurityService; } // 实现MethodSecurityExpressionOperations的必要方法 @Override public void setFilterObject(Object filterObject) { this.filterObject = filterObject; } @Override public Object getFilterObject() { return filterObject; } @Override public void setReturnObject(Object returnObject) { this.returnObject = returnObject; } @Override public Object getReturnObject() { return returnObject; } @Override public Object getThis() { return this; } }
然后配置方法安全表达式处理器:
@Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration { @Autowired private TenantSecurityService tenantSecurityService; @Override protected MethodSecurityExpressionHandler createExpressionHandler() { DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler(); handler.setSecurityExpressionRootFactory(authentication -> { CustomSecurityExpressionRoot root = new CustomSecurityExpressionRoot(authentication); root.setTenantSecurityService(tenantSecurityService); return root; }); return handler; } }
之后就可以这样使用(直接引用静态常量避免字符串拼接):
@PreAuthorize("hasTenantAuthority(T(com.yourpackage.Authorities).PRODUCTS_WRITE)")
2. 批量权限元注解
如果有一组常用的权限组合,可以定义更具体的元注解,进一步减少重复代码:
@AuthorityRequired(Authorities.PRODUCTS_WRITE) public @interface ProductWritePermission {}
在方法上直接使用:
@PostMapping @ProductWritePermission public Product createProduct(...) { ... }
3. AOP实现权限校验
如果不想依赖Spring Security的注解体系,可以用Spring AOP自定义切面,拦截带有AuthorityRequired注解的方法执行权限校验:
@Aspect @Component public class AuthorityAspect { @Autowired private TenantSecurityService tenantSecurityService; @Around("@annotation(authorityRequired)") public Object checkAuthority(ProceedingJoinPoint joinPoint, AuthorityRequired authorityRequired) throws Throwable { String requiredAuthority = authorityRequired.value(); if (!tenantSecurityService.hasAuthority(requiredAuthority)) { throw new AccessDeniedException("Insufficient authority"); } return joinPoint.proceed(); } }
这种方式更轻量,但需要自行处理异常和认证上下文的获取。
内容的提问来源于stack exchange,提问作者Kevin
相关产品推荐
相关产品推荐

