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。
相关推荐
Elieal8 分钟前
SpringBoot 数据层开发与企业信息管理系统实战
java·spring boot·后端
识君啊8 分钟前
MyBatis-Plus 逻辑删除导致唯一索引冲突的解决方案
java·spring boot·mybatis·mybatis-plus·唯一索引·逻辑删除
Coder_Boy_9 分钟前
Java开发者破局指南:跳出内卷,借AI赋能,搭建系统化知识体系
java·开发语言·人工智能·spring boot·后端·spring
QT.qtqtqtqtqt11 分钟前
SQL注入漏洞
java·服务器·sql·安全
NE_STOP11 分钟前
spring6-代理模式和AOP
spring
独自破碎E14 分钟前
BISHI23 小红书推荐系统
java·后端·struts
xqqxqxxq16 分钟前
Java IO 核心:BufferedReader/BufferedWriter & PrintStream/PrintWriter 技术笔记
java·笔记·php
Aric_Jones17 分钟前
idea使用.env运行SpringBoot项目
java·spring boot·intellij-idea
gustt19 分钟前
构建全栈AI应用:集成Ollama开源大模型
前端·后端·ollama
刘一说24 分钟前
Java 中实现多租户架构:数据隔离策略与实践指南
java·oracle·架构