在基于 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: -
这是
ssBean 中定义的一个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)
当你写下这行注解时,后端发生了什么?
- 定义 Bean (The Bean) :
首先,必须有一个 Bean 被注册为ss。
java
// SecurityConfiguration.java 或者某个配置类
@Bean("ss") // 强行指定 Bean 名字为 ss
public PermissionService permissionService() {
return new PermissionServiceImpl();
}
- 实现逻辑 (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);
}
}
- AOP 拦截 (The Interceptor) :
当请求到达 Controller 方法前,Spring Security 的 AOP 拦截器截获请求,解析 SpEL,执行ss.hasPermission()。
- 返回
true-> 放行,进入 Controller。 - 返回
false-> 拦截,抛出 403 Forbidden。