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。
相关推荐
上弦月-编程7 小时前
C语言指针从入门到实战
java·jvm·算法
Cyan_RA97 小时前
SpringMVC 请求数据绑定与参数映射 详解
java·后端·spring·mvc·springmvc·映射请求数据
逻辑驱动的ken7 小时前
Java高频面试考点场景题20
java·开发语言·深度学习·面试·职场和发展
GISer_Jing7 小时前
AI全栈工程师知识体系全景:从前后端核心架构到落地项目全拆解
前端·人工智能·后端·ai编程
bzmK1DTbd7 小时前
Java游戏服务器:Netty框架的高并发网络通信
java·服务器·游戏
longxibo7 小时前
【Flowable 7.2 源码深度解析与实战-前言】
java·后端·流程图
全栈小刘7 小时前
ChatGPT账号打通OpenClaw?Codex又整了个“电子宠物”,开发者这下真坐不住了
后端
小龙报7 小时前
【Coze-AI智能体平台】低代码省时高效:Coze 应用开发全流程指南
java·人工智能·python·深度学习·低代码·chatgpt·交互
陈随易7 小时前
bun将会支持Bun.image,你怎么看?
前端·后端·程序员
念何架构之路8 小时前
Go Web基础和Http演进
开发语言·后端·golang