授权:方法安全 @PreAuthorize 与角色模型
URL 级授权(第 29 章)粒度太粗:同是 /api/orders,删除与查询权限不同;同是查询,只许看自己的数据。方法级安全把授权下沉到 Service/Controller 方法上,用注解 + SpEL 表达权限,示例基于 Security 6 / Boot 3。
开启方法安全
@Configuration
@EnableMethodSecurity // 开启 @PreAuthorize/@PostAuthorize 等
public class MethodSecurityConfig {
}
@PreAuthorize 基础用法
@Service
public class OrderService {
@PreAuthorize("hasRole('ADMIN')") // 角色判断(自动带 ROLE_ 前缀)
public void deleteAll() {
// 只有 ADMIN 能执行
}
@PreAuthorize("hasAnyRole('ADMIN','OPERATOR')")
public void refund(String orderId) {
// ADMIN 或 OPERATOR
}
@PreAuthorize("hasAuthority('order:write')") // 细粒度权限点
public void update(Order order) {
// 需要 order:write 权限
}
}
方法执行前先求值 SpEL,不通过抛 AccessDeniedException(表现成 403)。
结合方法参数:数据级权限
@PreAuthorize("hasRole('ADMIN') or #order.ownerId == authentication.principal.id")
public void cancel(Order order) {
// 管理员或订单本人可取消;#参数名 引用方法入参
}
authentication、principal 是 SpEL 内置对象。要在表达式里用参数名,编译需保留参数名(Boot 的 spring-boot-maven-plugin 默认带 -parameters)。
其他注解与回看
@PostAuthorize:方法返回后按返回值再校验(可判断"是否本人数据",配合returnObject);@PreFilter/@PostFilter:对集合入参/返回值按表达式过滤;- 旧注解
@Secured("ROLE_ADMIN")与 JSR-250 的@RolesAllowed属兼容方案,需要@EnableMethodSecurity(securedEnabled = true, jsr250Enabled = true)开启。
角色模型设计
- 角色(role):粗粒度身份,如 ADMIN/USER;代码里统一转成带
ROLE_前缀的权限存储,hasRole('ADMIN')与hasAuthority('ROLE_ADMIN')等价; - 权限(authority):细粒度操作点,如
order:write、user:delete,适合菜单/按钮级控制; - 常见 RBAC 结构:用户 — 角色 — 权限 多对多,登录时把用户全部权限(含角色)装进 Authentication;
- JWT 方案里对应把 roles/perms 放进 claim,由 JwtAuthenticationConverter 转成 authorities(第 31 章);
- 角色层级可省大量判断:
ROLE_ADMIN > ROLE_USER用 RoleHierarchy 声明(6.3+ 提供 fromHierarchy 静态工厂,具体 API 以官方文档为准)。
两个易错点
- 方法安全靠 AOP 代理:同类内部
this.deleteAll()自调用会绕过代理、注解不生效(与 @Transactional 同源,见第 43 章); - 注解方法必须是通过代理调用的 public Spring Bean 方法。
小结:@EnableMethodSecurity + @PreAuthorize 是方法级授权的标准姿势;设计上区分"角色 + 权限点",数据级场景善用 #参数 与 principal 表达式。