【SpringSecurity新手村系列】(7)基于资源权限码(Authority)的接口权限控制实战

第七章 基于资源权限码(Authority)的接口权限控制实战

本章完成从"用户-角色-权限资源"数据模型到 @PreAuthorize 方法级拦截的完整闭环。和上一章"角色控制(Role)"不同,本章重点是 资源权限码(Authority) ,即 clue:listclue:edit 这类细粒度权限。你将得到一套能直接用于企业项目的权限控制方案,同时规避 Controller 未注册Mapper SQL 字段写错权限码字段映射错位 等高频坑位。

一、问题切入:为什么从角色升级到资源权限

在 RBAC 的第一阶段,我们常用 hasRole('ADMIN') 做控制,这对"大权限分层"足够,但在真实业务里会出现三个痛点:

  1. 同一角色下能力差异大:同是运营角色,有人可导出、有人只能查看
  2. 接口粒度需要更细/api/clue/list/api/clue/edit/api/clue/del 权限应独立
  3. 角色扩展会爆炸:用角色拼业务能力会导致角色数量快速膨胀

因此,本章采用更细粒度的控制方式:

  • 角色负责"人群分组"
  • 权限码(Authority)负责"具体动作授权"

也就是:用户 -> 角色 -> 资源权限码 -> 接口拦截

二、权限模型与调用链

从项目结构看,本章已经具备"权限码驱动授权"的核心骨架:

  • Users 实现 UserDetails,并在 getAuthorities() 返回权限集合
  • PermissionsMapper.selectByUserId 负责查出用户拥有的权限
  • ClueController 使用 @PreAuthorize("hasAuthority('...')") 做方法级权限控制
  • SecurityConfig 已开启 @EnableMethodSecurity

整体链路如下:

rust 复制代码
登录 -> UserServiceImpl.loadUserByUsername()
    -> 查询用户 permissionsList
    -> Users.getAuthorities() 转成 GrantedAuthority
    -> 放入 Authentication
    -> 调用接口时 @PreAuthorize(hasAuthority(...)) 匹配

只要这条链每一环都打通,方法级鉴权就能稳定生效。

三、核心代码解读(按实际项目)

3.1 开启方法级权限拦截

SecurityConfig 上的配置是关键总开关:

less 复制代码
@EnableMethodSecurity
@Configuration
public class SecurityConfig {
    // ...
}

没有这个注解,@PreAuthorize 不会生效。

同时项目里保留了通用登录校验:

scss 复制代码
.authorizeHttpRequests((authorizeHttpRequests) ->{
    authorizeHttpRequests
        .requestMatchers("/tologin", "/common/captcha").permitAll()
        .anyRequest().authenticated();
})

意思是:先保证"必须登录",再在方法层做"是否有某个资源权限码"。

3.2 用户权限装载:从数据库到 GrantedAuthority

UserServiceImpl 在登录时装载用户权限:

ini 复制代码
List<Permissions> permissionsList = permissionsMapper.selectByUserId(users.getId());
users.setPermissionsList(permissionsList);
return users;

Users#getAuthorities() 再把 permissionsList 转为 Spring Security 可识别的权限集合:

csharp 复制代码
for (Permissions permissions : this.permissionsList) {
    authorities.add(new SimpleGrantedAuthority(permissions.getCode()));
}

注意这里的关键点:传给 SimpleGrantedAuthority 的必须是最终参与匹配的权限码字符串

3.3 方法级权限控制写法

ClueController 采用资源权限码做拦截:

kotlin 复制代码
@PreAuthorize("hasAuthority('clue:list')")
@RequestMapping("/api/clue/list")
public String clueList(){
    return "clueList";
}

@PreAuthorize("hasAnyAuthority('clue:export','clue:download')")
@RequestMapping("/api/clue/export")
public String clueExport(){
    return "clueExport";
}

这套写法比 hasRole 更细,能够直接落到业务动作级别。

四、ss07 当前实现里的关键坑位(必须修)

下面这些是我根据你的 ss07 源码直接定位出的实战问题。

4.1 ClueController 缺少 @Controller 注解

当前类定义只有:

kotlin 复制代码
public class ClueController {

没有 @Controller@RestController,Spring 不会注册该 Bean,@PreAuthorize 自然也不会触发。

建议改为:

kotlin 复制代码
@Controller
public class ClueController {

4.2 Users.getAuthorities() 使用了不存在的字段 permissions.getCode()

Permissions 实体字段是:

  • permKey
  • permName
  • permType
  • path
  • method

并没有 code 字段,因此当前代码会编译失败或运行异常。应改为:

csharp 复制代码
authorities.add(new SimpleGrantedAuthority(permissions.getPermKey()));

并保证数据库 perm_key 存的就是 clue:listclue:edit 等权限码。

4.3 PermissionsMapper.xml SQL 有别名与字段拼写错误

当前 SQL 片段:

csharp 复制代码
from permissions
left join role_permissions rp on p.id = rp.premission_id

问题有两个:

  1. permissions 没有起别名 p,却用了 p.id
  2. premission_id 疑似拼写错误(常见应为 permission_id

建议修正示例:

csharp 复制代码
select p.*
from permissions p
left join role_permissions rp on p.id = rp.permission_id
left join roles r on rp.role_id = r.id
left join user_roles ur on r.id = ur.role_id
where ur.user_id = #{id}

4.4 LoginInfoUtil 强转有风险

当前实现直接:

kotlin 复制代码
return (Users) authentication.getPrincipal();

未登录或匿名场景会触发类型转换异常。建议加空值和类型判断。

五、推荐的"资源权限控制"标准写法

5.1 数据层建议

permissions 表建议至少包含:

  • perm_key:权限码(如 clue:list
  • perm_name:权限名称(如"线索查看")
  • pathmethod:可选,用于审计或动态鉴权

并通过 role_permissions 建立角色到权限资源的关联。

5.2 代码层建议

  1. 登录阶段只做一次权限装载
  2. 统一用 hasAuthority / hasAnyAuthority 控制动作权限
  3. 接口命名和权限码保持一致规则(资源:动作)

示例:

makefile 复制代码
clue:list
clue:view
clue:add
clue:edit
clue:del
clue:export

5.3 命名规范建议

  • 推荐统一:模块:动作
  • 动作用有限集合:list/view/add/edit/del/export/approve
  • 不要混用大小写,统一小写,降低误配风险

六、与角色控制(Role)的区别与配合

维度

Role(角色)

Authority(资源权限码)

粒度

典型表达式

hasRole('ADMIN')

hasAuthority('clue:edit')

适用场景

模块级入口、菜单级隔离

接口级动作控制

是否自动加前缀

hasRole 自动补 ROLE_

hasAuthority 不补前缀

最佳实践是二者结合:

  • URL 层做基础登录和大粒度限制
  • 方法层做资源动作级控制

七、快速自测清单

  1. 修复 ClueController 注解后,确认 /api/clue/* 能正确映射
  2. 登录普通用户,访问 @PreAuthorize("hasAuthority('clue:list')") 接口,验证放行
  3. 同一用户访问 clue:del,验证 403
  4. 赋予该用户 clue:del 权限后重新登录,验证可通过
  5. 打印 Authentication#getAuthorities(),确认权限码来自 perm_key
  6. 故意写错权限码(如 clue:delete),验证拦截行为符合预期

八、总结

本章的核心目标:基于资源权限码的精细化接口控制。关键不是写了多少注解,而是把"数据库权限码 -> UserDetails 权限集合 -> 方法拦截表达式"这条链打通并保持一致。

如果这条链任一处命名不一致、字段错位、Bean 未注册,权限看起来"写了但不生效"。反过来,一旦链路统一,你就能非常稳定地扩展到更多模块(客户、合同、回款、审批等),并把安全控制从"角色粗分"升级为"动作级可审计控制"。

编辑者 :Flittly
更新时间:2026年4月

相关推荐
ECT-OS-JiuHuaShan2 小时前
哲学的本质,是递归因果
java·开发语言·人工智能·科技·算法·机器学习·数学建模
alxraves2 小时前
医疗器械管代的职责
安全·健康医疗·制造
倾听一世,繁花盛开2 小时前
Java语言程序设计——篇十三(1)
java·开发语言·ide·eclipse
大腕先生2 小时前
通用分页超详细介绍(附带源代码解析&页面展示效果)
xml·java·linux·服务器·开发语言·前端·idea
A_aspectJ2 小时前
如何抓住Java开发岗的市场红利?从需求端反推学习路径
java·开发语言·职场和发展
zhenxin01222 小时前
GitSubmodule避坑指南:从入门到精通
spring boot·后端·spring
XS0301062 小时前
Java 基础(九) IO流
java·开发语言·php
_Evan_Yao2 小时前
缓存金字塔上的红色闪电:Redis 如何借力 CPU 的 L1/L2/L3 与 TLB 飞驰
java·数据库·redis·后端·缓存
alxraves2 小时前
医疗器械质量管理体系信息系统的需求
大数据·安全·健康医疗·制造