Spring Security @PreAuthorize 与自定义 @ss.hasPermission 权限控制

在基于 Spring Boot + Spring Security 的企业级项目中,我们经常在 Controller 层看到类似 @PreAuthorize("@ss.hasPermission('system:user:query')") 的注解。

这行代码虽短,却完美融合了 AOP 切面SpEL 表达式 以及 RBAC 模型。本文将为你拆解它的底层原理及使用技巧。

一、 核心概念拆解

这个注解由两部分组成:外层的 Spring Security 注解和内层的 SpEL 表达式。

1. @PreAuthorize(...)
  • 身份 :这是 Spring Security 提供的方法级安全注解

  • 作用时机 :它会在方法被调用之前执行。

  • 功能:它接收一个布尔值(True/False)。

  • 如果表达式计算结果为 true,方法正常执行。

  • 如果为 false,框架抛出 AccessDeniedException(访问拒绝异常),前端通常收到 403 状态码。

  • 开启方式 :需在启动类或配置类上添加 @EnableGlobalMethodSecurity(prePostEnabled = true) (Spring Boot 2.x) 或 @EnableMethodSecurity (Spring Boot 3.x)。

2. @ss.hasPermission('...')

这是一段 SpEL (Spring Expression Language) 表达式,它的含义是:调用 Spring 容器中名为 ss 的 Bean,执行它的 hasPermission 方法。

  • @ss

  • @ 符号在 SpEL 中表示引用 Bean

  • ss 是 Bean 的名称(Alias)。在 Yudao 框架中,它通常指代 PermissionService 的实现类(Security 层面的封装)。

  • 注:为什么叫 ss?通常取自 S pring Security 的首字母缩写。

  • hasPermission

  • 这是 ss Bean 中定义的一个 public boolean 方法。

  • 它负责具体的鉴权逻辑:获取当前登录用户 -> 读取用户拥有的权限列表 -> 判断是否包含传入的参数。

  • '...'

  • 传递给方法的字符串参数,即权限标识 (如 system:user:query),对应数据库 system_menu 表的 permission 字段。


二、 具体代码示例

以下展示了三种常见的用法场景:

场景 1:单权限控制(最常见)

只有拥有"查询用户"权限的管理员,才能调用分页接口。

java 复制代码
@GetMapping("/page")
// 翻译:喂,ss Bean,帮我查查这个人有没有 'system:user:query' 这把钥匙?
@PreAuthorize("@ss.hasPermission('system:user:query')")
public CommonResult<PageResult<UserRespVO>> getUserPage(@Valid UserPageReqVO reqVO) {
    return success(userService.getUserPage(reqVO));
}
场景 2:多权限控制(逻辑或 / 逻辑与)

SpEL 支持逻辑运算符 || (OR) 和 && (AND)。

示例 A:拥有"导出"或者"读取"权限均可访问

java 复制代码
@GetMapping("/export")
@PreAuthorize("@ss.hasPermission('system:user:export') || @ss.hasPermission('system:user:read')")
public void exportUser(...) { ... }

示例 B:必须同时拥有"创建"和"审批"权限才可访问

java 复制代码
@PostMapping("/create-high-level")
@PreAuthorize("@ss.hasPermission('system:user:create') && @ss.hasPermission('system:approval:pass')")
public void createHighLevelUser(...) { ... }
场景 3:自定义鉴权逻辑(调用其他方法)

既然 @ss 是一个 Bean,你完全可以在里面写其他方法。比如判断是否是超级管理员。

java 复制代码
// 假设 PermissionService 中写了一个 isSuperAdmin() 方法
@DeleteMapping("/delete-core")
@PreAuthorize("@ss.isSuperAdmin()") 
public void deleteCoreData() {
    // 只有超管能删核心数据
}

三、 底层实现原理(Backstage)

当你写下这行注解时,后端发生了什么?

  1. 定义 Bean (The Bean) :
    首先,必须有一个 Bean 被注册为 ss
java 复制代码
// SecurityConfiguration.java 或者某个配置类
@Bean("ss") // 强行指定 Bean 名字为 ss
public PermissionService permissionService() {
    return new PermissionServiceImpl();
}
  1. 实现逻辑 (The Logic) :
    PermissionServiceImpl 实现了具体的校验。
java 复制代码
public class PermissionServiceImpl implements PermissionService {
    public boolean hasPermission(String permission) {
        // 1. 获取当前登录用户的 ID
        Long userId = SecurityFrameworkUtils.getLoginUserId();
        // 2. 如果是超管 (ID=1),直接放行
        if (userId == 1L) return true;
        // 3. 查缓存/DB,看该用户的权限集合里有没有这个字符串
        return permissionApi.hasAnyPermissions(userId, permission);
    }
}
  1. AOP 拦截 (The Interceptor) :
    当请求到达 Controller 方法前,Spring Security 的 AOP 拦截器截获请求,解析 SpEL,执行 ss.hasPermission()
  • 返回 true -> 放行,进入 Controller。
  • 返回 false -> 拦截,抛出 403 Forbidden。
相关推荐
我爱学习好爱好爱2 小时前
Prometheus监控栈 监控java程序springboot
java·spring boot·prometheus
老华带你飞2 小时前
考试管理系统|基于java+ vue考试管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot·后端
2501_921649492 小时前
股票 API 对接,接入美国纳斯达克交易所(Nasdaq)实现缠论回测
开发语言·后端·python·websocket·金融
URBBRGROUN4672 小时前
Spring AI @ToolParam 扩展注解改造实践
大数据·人工智能·spring
Grassto2 小时前
从 GOPATH 到 Go Module:Go 依赖管理机制的演进
开发语言·后端·golang·go
阿蒙Amon2 小时前
C#每日面试题-属性和特性的区别
java·面试·c#
懒惰蜗牛2 小时前
Day66 | 深入理解Java反射前,先搞清楚类加载机制
java·开发语言·jvm·链接·类加载机制·初始化
xl-xueling3 小时前
从快手直播故障,看全景式业务监控势在必行!
大数据·后端·网络安全·流式计算
kevinzeng3 小时前
Java的类加载过程
后端