授权:方法安全 @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) {
    // 管理员或订单本人可取消;#参数名 引用方法入参
}

authenticationprincipal 是 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:writeuser: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 表达式。

笔记加载中…