方法级安全 @PreAuthorize 如何工作?RBAC 权限模型如何设计?

结论先行:方法安全 = AOP 在业务方法执行前/后插入授权检查。@PreAuthorize 用 SpEL 表达式按“当前登录用户能否调用”裁决,失败抛 AccessDeniedException。落地常用 RBAC:用户→角色→权限三级模型,角色把权限打包,用户只挂角色,授权表达式尽量对着权限(authority)而非角色硬编码。

一、开启与注解

@Configuration
@EnableMethodSecurity   // Security 6.x;替代旧 @EnableGlobalMethodSecurity
public class MethodSecurityConfig {}
@PreAuthorize("hasRole('ADMIN')")                       // 角色:自动带 ROLE_ 前缀
@PreAuthorize("hasAuthority('order:update')")           // 权限
@PreAuthorize("@permService.check(#order.userId)")      // 自定义 Bean + 参数
public void updateOrder(@Param("order") Order order) { }

二、关键注解与语义

注解时机说明
@PreAuthorize执行前不满足直接拒绝,最常用
@PostAuthorize执行后可依据返回值再校验
@PreFilter / @PostFilter前后对集合参数/返回值过滤
@Secured / @RolesAllowed执行前旧式角色写法,语义较简单
  • SpEL 可用对象:authentication、principal、参数名(需 -parameters 编译或 @Param);
  • hasRole 与 hasAuthority:hasRole('ADMIN') 内部等价 hasAuthority('ROLE_ADMIN');
  • 表达式可组合:hasAnyRole、hasAnyAuthority、!、&&、||。

三、RBAC 权限模型

用户(USER) ──多对多──> 角色(ROLE) ──多对多──> 权限(PERMISSION)
权限示例:order:create / order:update / order:delete(资源:操作)

设计要点:

  1. 接口不做死权限名,菜单/按钮由权限点驱动,改角色即生效;
  2. 角色支持层级(RoleHierarchy)时注意别把层级写进 JWT 等静态缓存;
  3. 授权应同时覆盖 URL 层(SecurityFilterChain 规则)与方法层(@PreAuthorize),纵深防御;
  4. 超级管理员用“角色含全部权限”表达,避免到处判断 isAdmin;
  5. 权限变更及时性:无状态 JWT 里嵌权限时需配合刷新或每次查库,避免“改权限不生效”事故。

常见追问 / 记忆点

  • 追问:URL 鉴权与 @PreAuthorize 区别?答:前者过滤器链做粗粒度门禁,后者 AOP 做方法级细粒度,通常两者都配。
  • 追问:为什么推荐按权限而非角色授权?答:角色是权限集合的别名,直接依赖角色会把权限变更耦合进代码。
  • 记忆点:方法安全=AOP+SpEL;RBAC 三级“用户-角色-权限”,表达式用 authority 不硬编码角色名。
笔记加载中…