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

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

本章完成从"用户-角色-权限资源"数据模型到 @PreAuthorize 方法级拦截的完整闭环。和上一章"角色控制(Role)"不同,本章重点是 资源权限码(Authority) ,即 clue:list、clue: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:list、clue: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:权限名称(如"线索查看")
  • path、method:可选,用于审计或动态鉴权

并通过 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月

相关推荐
weixin_460443563 小时前
2026年新修订《网络安全法》实施:Java私有化考试系统如何做好SBOM、漏洞治理与安全升级?
java·开发语言·在线考试系统·企业培训考试系统·宏远培训考试系统·国企培训考试系统·国产化培训系统
海鸥-w6 小时前
什么是AOP
java
EasyGBS6 小时前
摄像头还在线,画面已经不能用了?国标GB28181视频平台EasyGBS视频质量诊断怎么用
服务器·安全·音视频
Cc.Y6 小时前
Java零基础入门:可变字符串与包装类:StringBuilder、StringBuffer 与 通讯录管理系统实战
java·开发语言·python
paopaokaka_luck6 小时前
基于springboot+vue的智慧家政管理系统(AI问答、 WebSocket实时沟通、ECharts运营分析、订单规则与服务追踪)
java·javascript·spring boot·数据分析·echarts·mybatis
sm_926787056 小时前
RFID 标签打印的技术实现要点与二次开发实践
java·大数据·前端·c++·编辑器
阿森_dev6 小时前
2027 届毕设选题怎么选:30 个 Java 方向选题的实现难度排序
java·开发语言·spring boot·毕业设计·课程设计
Sarvartha6 小时前
Lambda 表达式和函数式接口
java·开发语言
计算机毕业设计杰瑞7 小时前
【计算机毕设精品】基于SpringBoot的一体化教务与学业质量管理系统的设计与实现,附源码_增删改查_java项目_可完美运行部署_网站项目
java·spring boot·课程设计
迪康Defender7 小时前
终端安全审批体系设计:如何平衡安全管控与业务连续性
运维·服务器·安全·运维开发·学习方法·业界资讯·远程工作