1. 🔐 Spring Security 权限控制详解

1.1. 知识点讲解(通俗版)

想象一下,你进入一家高级俱乐部:

  • 门口保安(Security Filter) - 检查你的会员卡(Token验证)
  • VIP区域管理员(@PreAuthorize) - 检查你是否有权限进入特定区域
  • 普通区域 - 所有会员都可以进入
  • SVIP区域 - 只有 SVIP会员才能进入
  • 总统套房 - 只有顶级会员才能进入

在PmHub项目中,@PreAuthorize 就是这个"区域管理员",它会在方法执行之前检查用户是否有相应的权限。


1.2. 核心概念(通俗版)

公司门禁系统来类比:

  1. 员工卡(Token) - 每个员工有一张卡,卡里记录了:
    • 你是谁(用户信息)
    • 你能进哪些房间(权限列表)
  1. 门禁规则(@PreAuthorize) - 每个房间门口有个读卡器:
    • "project:manage:add" = 只有有"项目管理-新增"权限的人能进
    • "system:user:edit" = 只有有"系统管理-用户编辑"权限的人能进
  1. 权限管理员(PermissionService) - 负责检查你的卡是否有权限

1.3. 代码流程详解

1.3.1. 第一步:配置启用权限控制

SecurityConfig.java 中:

@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)

这个注解就像开启全公司的门禁系统prePostEnabled = true 表示启用 @PreAuthorize@PostAuthorize 注解。

12.3.2. 第二步:注册权限检查服务

PermissionService.java 中:

@Service("ss")  // ← 注意这个名字 "ss"
public class PermissionService {
    public boolean hasPermi(String permission) {
        // 获取当前登录用户
        LoginUser loginUser = SecurityUtils.getLoginUser();
        // 检查用户的权限列表中是否包含所需权限
        return hasPermissions(loginUser.getPermissions(), permission);
    }
}

为什么叫 "ss"?

  • "ss" 是 Spring Security 的缩写
  • 这个Bean名称会在SpEL表达式中使用

1.3.3. 第三步:Controller中使用权限注解

ProjectController.java 中:

@PreAuthorize("@ss.hasPermi('project:manage:add')")
@PostMapping("/add")
public AjaxResult add(@RequestBody Project project) {
    projectService.saveProject(project);
    return AjaxResult.success();
}

这个注解的含义:

  • @PreAuthorize - 在方法执行之前进行权限检查
  • @ss - 调用名为 "ss" 的Bean(即PermissionService)
  • .hasPermi('project:manage:add') - 调用hasPermi方法,传入权限字符串

1.3.4. 第四步:权限检查流程

当一个请求到达时:

1. 用户登录 → 获得Token
   ↓
2. Token中包含用户信息和权限列表
   例如:["project:manage:add", "project:manage:edit", "system:user:list"]
   ↓
3. 用户访问 /project/add 接口
   ↓
4. Spring Security拦截请求
   ↓
5. 执行 @PreAuthorize("@ss.hasPermi('project:manage:add')")
   ↓
6. PermissionService.hasPermi() 检查:
   - 从Token中获取用户权限列表
   - 判断是否包含 "project:manage:add"
   ↓
7. 结果:
   - ✅ 有权限 → 执行Controller方法
   - ❌ 无权限 → 返回403 Forbidden

1.4. 权限字符串的命名规范

权限字符串采用三段式命名:模块:功能:操作

例如:

  • project:manage:add - 项目管理模块-管理功能-新增操作
  • project:manage:edit - 项目管理模块-管理功能-编辑操作
  • project:manage:delete - 项目管理模块-管理功能-删除操作
  • system:user:list - 系统管理模块-用户功能-列表查询

这些权限存储在数据库的 sys_menu 表中,perms 字段:


1.5. 完整的权限控制流程图

数据库设计:
┌─────────────────────────────────────┐
│ sys_menu (菜单权限表)                │
├──────────┬──────────┬───────────────┤
│ menu_id  │ perms    │ menu_type     │
├──────────┼──────────┼───────────────┤
│ 1        │          │ M (目录)      │
│ 2        │          │ C (菜单)      │
│ 3        │ project: │ F (按钮)      │
│          │ manage:  │               │
│          │ add      │               │
└──────────┴──────────┴───────────────┘
         ↓
用户-角色-菜单关联:
┌──────────┐    ┌──────────┐    ┌──────────┐
│ sys_user │───→│sys_user_ │───→│ sys_role │
│ (用户表) │    │ role     │    │ (角色表) │
└──────────┘    └──────────┘    └──────────┘
                                     │
                                     ↓
                              ┌──────────┐
                              │sys_role_ │
                              │ menu     │
                              └──────────┘
                                     │
                                     ↓
                              ┌──────────┐
                              │ sys_menu │
                              │ (菜单表) │
                              └──────────┘
         ↓
用户登录时加载权限:
LoginUser {
    userId: 1,
    username: "admin",
    permissions: [
        "project:manage:add",
        "project:manage:edit",
        "system:user:list"
    ]
}
         ↓
Token中存储权限信息(JWT)
         ↓
请求拦截检查:
@PreAuthorize("@ss.hasPermi('project:manage:add')")
         ↓
PermissionService.hasPermi() 检查:
- 从SecurityContext获取当前用户
- 获取用户的permissions集合
- 判断是否包含所需权限
         ↓
结果:
✅ 有权限 → 执行方法
❌ 无权限 → 抛出异常,返回403

1.6. 特殊权限标识

PermissionService.java 中有一个特殊权限:

private static final String ALL_PERMISSION = "*:*:*";

超级管理员权限:如果用户的权限列表中包含 "*:*:*",则该用户拥有所有权限,可以访问任何接口。


Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐