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

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

第七章 基于资源权限码(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

整体链路如下:

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

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


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

3.1 开启方法级权限拦截

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

@EnableMethodSecurity
@Configuration
public class SecurityConfig {
   
    // ...
}

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

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

.authorizeHttpRequests((authorizeHttpRequests) ->{
   
    authorizeHttpRequests
        .requestMatchers("/tologin", "/common/captcha").permitAll()
        .anyRequest().authenticated();
})

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

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

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

List<Permissions> permissionsList = permissionsMapper.selectByUserId(users.getId());
users.setPermissionsList(permissionsList);
return users;

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

for (Permissions permissions : this.permissionsList) {
   
    authorities.add(new SimpleGrantedAuthority(permissions.getCode()));
}

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

3.3 方法级权限控制写法

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

@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 注解

当前类定义只有:

public class ClueController {
   

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

建议改为:

@Controller
public class ClueController {
   

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

Permissions 实体字段是:

  • permKey
  • permName
  • permType
  • path
  • method

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

authorities.add(new SimpleGrantedAuthority(permissions.getPermKey()));

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

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

当前 SQL 片段:

from permissions
left join role_permissions rp on p.id = rp.premission_id

问题有两个:

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

建议修正示例:

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 强转有风险

当前实现直接:

return (Users) authentication.getPrincipal();

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


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

5.1 数据层建议

permissions 表建议至少包含:

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

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

5.2 代码层建议

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

示例:

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月

目录
相关文章
|
2月前
|
安全 前端开发 搜索推荐
【SpringSecurity新手村系列】(3)自定义登录页与表单认证
自定义登录页与表单认证本文围绕自定义登录页展开,详解 formLogin、loginProcessingUrl 与跳转配置,重点解释 CSRF 隐藏域的作用、校验原理及常见错误,帮助你稳定完成表单登录改造。
221 6
|
2月前
|
JSON 安全 Java
【SpringSecurity新手村系列】(6)基于角色的权限控制、权限拦截注解与自定义无权限页面
本章在 RBAC 角色体系上,开启 @EnableMethodSecurity,使用 @PreAuthorize 配合 hasRole / hasAnyRole 实现 Controller 方法级拦截,并配置自定义 403 无权限页面,重点拆解 ROLE_ 前缀重复拼接的常见坑位。
267 1
|
2月前
|
SQL 关系型数据库 MySQL
【全网最详细】Navicat下载免费版 | Navicat数据库管理工具安装图解(2026最新)
Navicat是一款专业、直观的数据库管理工具,支持MySQL、PostgreSQL、Oracle等十余种主流数据库。图形化界面替代命令行,轻松实现建表、SQL编写、数据导入导出与同步,大幅提升开发与DBA工作效率。(239字)
10346 132
|
2月前
|
Java 数据库连接 数据库
【SpringSecurity新手村系列】(2)整合 MyBatis 实现数据库认证
本文讲解如何将 MySQL 与 MyBatis 接入 Spring Security,通过自定义 UserDetailsService 实现数据库认证,并说明 PasswordEncoder、Mapper/XML 与登录流程的关键实现点。
174 1
|
2月前
|
JSON 前端开发 API
【LangGraph新手村系列】(1)LangGraph 入门:StateGraph 与带记忆的 ReAct 循环
介绍 LangGraph 核心思想:用 StateGraph 把单次 LLM 调用串成可循环的 ReAct 工作流。通过节点、边与公共状态黑板,实现模型思考、工具调用、条件跳转的闭环,并引入检查点让 Agent 拥有跨轮次记忆。
611 1
【LangGraph新手村系列】(1)LangGraph 入门:StateGraph 与带记忆的 ReAct 循环
|
2月前
|
XML 安全 Java
【SpringSecurity新手村系列】(5)RBAC角色权限与账户状态校验
本章在数据库认证基础上,通过 RBAC 三表(用户-角色-权限)把角色与权限从数据库接入 Spring Security,并补齐账户状态字段(过期、锁定、启用等),实现“可登录 + 可授权 + 可控状态”的完整认证闭环。
206 4
|
1月前
|
人工智能 前端开发 数据挖掘
【LangGraph新手村系列】(5)时间旅行:浏览历史、分叉时间线与修改过去
解决"状态只能单向增长"的问题。用get_state_history()像Git log一样浏览全部历史检查点,用旧快照的values作为新thread输入实现时间线分叉,用update_state()在任意检查点位置注入新状态后继续执行。三种操作赋予Agent全生命周期的事后控制能力。
185 1
|
2月前
|
NoSQL Java 数据库
【SpringAIAlibaba新手村系列】(11)Embedding 向量化与向量数据库
本文围绕 Embedding 与向量数据库展开,讲解了文本向量化、相似度检索和 VectorStore 的基本用法,并结合 SimpleVectorStore 示例说明了 Spring 中自动装配与手动注册 Bean 的区别,为后续学习 RAG 打下基础。
959 5
【SpringAIAlibaba新手村系列】(11)Embedding 向量化与向量数据库
|
2月前
|
人工智能 数据可视化 机器人
OpenClaw一键部署攻略,手把手教你 “养龙虾”!
还在为部署OpenClaw踩坑发愁?“养龙虾”其实超简单!本文奉上阿里云一键云端部署攻略:全程可视化、零代码,仅两步——买预装服务器+填API密钥,5分钟即可拥有专属AI数字员工!支持微信/钉钉协同、文件处理、日程管理、代码辅助等,新手友好,成本低廉(新用户首月9.9元+7000万Token免费额度)。
683 25