方法级安全
→ 返回 Spring Boot 基础 | → 安全
方法级安全在 URL 级授权 之外提供更细粒度的权限控制,直接在 Service/Controller 方法上声明权限规则,通过 AOP 代理在方法调用前后执行权限检查。
启用
在配置类上添加 @EnableMethodSecurity(Spring Boot 3.x / Spring Security 6.x 推荐):
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(
prePostEnabled = true, // 启用 @Pre/@PostAuthorize(默认 true)
securedEnabled = true, // 启用 @Secured
jsr250Enabled = true // 启用 @RolesAllowed
)
public class SecurityConfig { ... }Spring Security 5.x 使用的是
@EnableGlobalMethodSecurity,6.x 已废弃,统一改用@EnableMethodSecurity。
核心注解
@PreAuthorize — 调用前检查(最常用)
// 角色检查
@PreAuthorize("hasRole('ADMIN')")
public void deleteUser(Long id) { ... }
// 多角色(任一满足)
@PreAuthorize("hasAnyRole('ADMIN', 'MANAGER')")
public List<User> listAll() { ... }
// 精确权限字符串
@PreAuthorize("hasAuthority('user:delete')")
public void deleteUser(Long id) { ... }
// 逻辑组合
@PreAuthorize("hasRole('ADMIN') or hasAuthority('order:export')")
public void exportOrders() { ... }
// 访问方法参数(#参数名)
@PreAuthorize("hasRole('ADMIN') or #userId == authentication.principal.id")
public UserProfile getProfile(Long userId) { ... }
// 访问对象字段
@PreAuthorize("#order.status == 'DRAFT' and hasRole('EDITOR')")
public void submitOrder(Order order) { ... }@PostAuthorize — 调用后检查返回值
// returnObject 指代返回值
@PostAuthorize("returnObject.owner == authentication.name")
public Document getDocument(Long id) { ... }
// 用于"只有资源所有者才能看到自己的数据"场景
@PostAuthorize("returnObject.userId == authentication.principal.id")
public Order getOrder(Long orderId) { ... }
@PostAuthorize方法已经执行并产生副作用后才做权限检查,不适合有写操作的方法。
@PreFilter / @PostFilter — 集合过滤
// 过滤入参集合(保留满足条件的元素)
@PreFilter("filterObject.owner == authentication.name")
public void batchDelete(List<Document> docs) { ... }
// 过滤返回集合
@PostFilter("filterObject.owner == authentication.name")
public List<Document> findAll() { ... }集合较大时性能差,建议在查询层直接加条件过滤。
@Secured(简单角色检查)
@Secured("ROLE_ADMIN") // 注意需要 ROLE_ 前缀
public void adminOperation() { ... }
@Secured({"ROLE_ADMIN", "ROLE_MANAGER"}) // 任一满足
public void managerOperation() { ... }@RolesAllowed(JSR-250)
@RolesAllowed("ADMIN") // 无需 ROLE_ 前缀
public void adminOperation() { ... }SpEL 表达式速查
方法级安全基于 SpEL 表达式,可用内置变量:
| 变量 | 类型 | 说明 |
|---|---|---|
authentication | Authentication | 当前认证对象 |
authentication.name | String | 当前用户名 |
authentication.principal | Object | Principal(通常是 UserDetails) |
authentication.authorities | Collection | 权限集合 |
principal | Object | authentication.principal 的快捷方式 |
#参数名 | 对应类型 | 方法入参(需编译保留参数名或用 -parameters) |
returnObject | 对应类型 | 方法返回值(仅 @PostAuthorize/@PostFilter) |
filterObject | 集合元素类型 | 当前迭代元素(仅 @PreFilter/@PostFilter) |
hasRole(r) | boolean | 是否拥有角色(自动补 ROLE_ 前缀) |
hasAuthority(a) | boolean | 是否拥有精确权限字符串 |
isAnonymous() | boolean | 是否匿名用户 |
isAuthenticated() | boolean | 是否已登录 |
permitAll | boolean | 始终 true |
denyAll | boolean | 始终 false |
自定义权限评估器
复杂权限逻辑(如从数据库查询 ACL)可实现 PermissionEvaluator:
// 1. 实现 PermissionEvaluator
@Component
public class DocumentPermissionEvaluator implements PermissionEvaluator {
private final DocumentAclService aclService;
@Override
public boolean hasPermission(Authentication auth,
Object targetDomainObject,
Object permission) {
if (targetDomainObject instanceof Document doc) {
return aclService.hasPermission(
auth.getName(), doc.getId(), permission.toString());
}
return false;
}
@Override
public boolean hasPermission(Authentication auth,
Serializable targetId,
String targetType,
Object permission) {
return aclService.hasPermission(
auth.getName(), (Long) targetId, permission.toString());
}
}
// 2. 注册
@Configuration
public class MethodSecurityConfig {
@Bean
public MethodSecurityExpressionHandler expressionHandler(
DocumentPermissionEvaluator evaluator) {
var handler = new DefaultMethodSecurityExpressionHandler();
handler.setPermissionEvaluator(evaluator);
return handler;
}
}
// 3. 使用 hasPermission()
@PreAuthorize("hasPermission(#docId, 'Document', 'READ')")
public Document getDocument(Long docId) { ... }
@PreAuthorize("hasPermission(#doc, 'WRITE')")
public void updateDocument(Document doc) { ... }数据权限(行级过滤)
结合 AOP 实现 SaaS 租户隔离或用户数据隔离:
@Aspect
@Component
public class DataScopeAspect {
@Around("@annotation(dataScope)")
public Object filter(ProceedingJoinPoint pjp, DataScope dataScope)
throws Throwable {
LoginUser user = SecurityUtils.getCurrentUser();
// 将当前用户的数据范围注入查询条件(如 MyBatis 拦截器)
DataScopeContextHolder.set(user.getDeptIds());
try {
return pjp.proceed();
} finally {
DataScopeContextHolder.clear();
}
}
}注意事项
- 方法级安全基于 AOP 代理,同类内部调用不生效(绕过代理),需通过
ApplicationContext获取代理对象或将方法移到另一个 Bean @PreAuthorize加在private方法上无效,必须是public- 参数名访问(
#参数名)需编译时保留参数名:javac -parameters或 Spring Boot 默认开启 - 与
@Transactional同时使用时,注意两个切面的@Order顺序
相关链接
- 安全 — 全局认证与 URL 级授权配置
- AOP — 方法级安全的底层代理机制
- SpEL表达式 — 权限表达式语法详解
- OAuth2与JWT — JWT 中 Claims 映射为权限