大家好,我是你们的源码拆解搭子。今天是 RuoYi-Vue-Pro 源码拆解系列的第二篇,我们来啃一块硬骨头------认证与权限模块。
上一篇我们拆解了框架层,知道了整个项目的"地基"是怎么打的。今天我们要往上一层,看看这个系统是怎么回答三个灵魂拷问的:你是谁?你能干什么?你能看到哪些数据?
废话不多说,直接上干货。
一、先说结论:这个模块到底在干什么?
认证与权限模块是 RuoYi-Vue-Pro 的"门禁系统",它横跨三个层次:
| 层次 | 回答的问题 | 核心机制 |
|---|---|---|
| 认证层 | 你是谁? | 账号密码/短信/社交登录 → 获取 OAuth2 Access Token |
| 功能权限层 | 你能干什么? | RBAC:用户→角色→菜单/按钮,控制能访问哪些页面、点击哪些按钮 |
| 数据权限层 | 你能看到哪些数据? | SQL 拦截器自动追加 WHERE 条件,控制能看到哪些行数据 |
划重点!!! 用一句话概括:这个模块决定了一个企业后台系统中"谁能进来、能做什么、能看到什么"的全部规则。
具体来说,第一层是认证层------用户通过账号密码、短信验证码、社交账号(微信/钉钉)等方式登录系统,获得一个 OAuth2 Access Token,后续每次请求都携带这个 Token 来证明身份。第二层是功能权限层------基于 RBAC 模型,通过"用户→角色→菜单/按钮"的关联关系,控制每个用户能访问哪些页面、能点击哪些按钮。第三层是数据权限层------在 SQL 层面自动追加过滤条件,让部门经理只能看到本部门的数据,普通员工只能看到自己的数据。
二、技术选型分析:为什么用这些技术,而不是别的?
2.1 认证方案:自研 OAuth2 Token vs. Spring Authorization Server vs. JWT
选择:基于 Spring Security + 自研 OAuth2 Token 体系,Token 为 UUID 字符串,存储在 MySQL + Redis 中。
替代方案:Spring Authorization Server(SAS)、Keycloak、JWT(无状态令牌)
RuoYi 的认证方案很有意思------它借用了 OAuth2 的概念框架(access_token、refresh_token、client、scope、grant_type),但实现是高度简化的自研版本。没有 JWT 签名验证,没有标准的 Authorization Code Flow 完整实现,Token 就是一个随机 UUID 字符串,存在数据库里,每次请求都去 Redis 查一下这个 Token 是否有效。
为什么这样设计?因为目标用户不需要完整的 OAuth2 协议栈。RuoYi 的核心场景是"企业内部后台管理系统",认证需求很简单:账号密码登录 → 拿 Token → 每次请求带 Token → Token 过期刷新。最多再加一个社交登录(微信/钉钉扫码)。这是一个单系统、单组织的场景。
如果用 Spring Authorization Server,配置复杂度会翻倍,使用者需要理解 OAuth2 的完整协议栈(grant type 协商、scope 管理、token endpoint、introspection 等),对初级开发者极不友好。如果用 JWT,虽然实现简单,但 JWT 一旦签发就无法主动失效------企业后台系统经常需要"踢人下线"、"禁用账号后立即生效",JWT 做不到这一点(除非额外引入黑名单机制,那又回到了有状态方案)。
注意:但这也是一个值得商榷的决策。如果企业未来需要对接其他系统(SSO 单点登录、开放平台 API),自研方案就需要大量改造。RuoYi 在微服务版本(yudao-cloud)中确实做了 SSO 支持,但单体版和微服务版在认证层的分裂会增加维护成本。
竞品对比:JeecgBoot 用了 Shiro + JWT;RuoYi-Cloud 用了 Spring Security OAuth2;Pig 用了 Spring Authorization Server(最标准的实现);Guns 用了 JWT。RuoYi 的选择是"够用就好"的务实路线。
2.2 权限模型:标准 RBAC + 5 级数据权限
选择:标准 RBAC(用户→角色→权限),辅以 5 级数据权限。
替代方案:ABAC(基于属性的访问控制)、ACL(访问控制列表)、ReBAC(基于关系的访问控制)
RBAC 是企业后台系统的事实标准。它的核心思想是:不直接给用户分配权限,而是给用户分配角色,角色再关联权限。这样当人员变动时,只需要调整角色分配,不需要逐个修改权限。
RuoYi 的 RBAC 实现有两个值得注意的设计决策:
第一,权限标识符采用三段式命名 :{module}:{entity}:{action},比如 system:user:create。这种命名方式直观、可扩展、易于理解。整个项目中有 250+ 个文件使用了 @PreAuthorize("@ss.hasPermission('xxx')") 注解,形成了一套统一的权限控制范式。
第二,数据权限做到了 5 级粒度:
| 级别 | 含义 | 典型场景 |
|---|---|---|
| 1 - ALL | 全部数据 | 系统管理员 |
| 2 - DEPT_CUSTOM | 指定部门 | 集团 HR 看几个子公司 |
| 3 - DEPT_ONLY | 本部门 | 部门经理看本部门 |
| 4 - DEPT_AND_CHILD | 本部门及下级 | 总监看所有下属部门 |
| 5 - SELF | 仅本人 | 普通员工 |
这比大多数竞品都做得细致。JeecgBoot 只有 3 级(全部、本部门、本人),Guns 也只有 3 级。RuoYi 多了"指定部门"和"本部门及下级"两个选项,更贴合中国企业的实际管理场景。
踩坑提醒:技术实现上,数据权限通过 MyBatis SQL 拦截器(JSqlParser)在 AST 层面自动改写 SQL,追加 WHERE dept_id IN (...) OR user_id = ? 条件。这种"无侵入"的设计让业务代码完全不需要关心数据权限逻辑,是一个工程上的亮点。
2.3 缓存策略:Spring Cache + Redis 多层缓存
选择:Spring Cache 注解(@Cacheable/@CacheEvict)+ Redis 作为缓存后端。
这里有一个值得学习的设计细节:Service 内部调用时使用 SpringUtil.getBean(getClass()) 获取自身的代理对象(代码中称为 getSelf() 模式),以确保 Spring AOP 的缓存注解能生效。因为 Spring 的代理机制,同一个类内部的方法调用不会经过代理,@Cacheable 注解会失效。RuoYi 通过这个技巧巧妙地解决了这个问题。
小贴士:虽然不够优雅(更优雅的方案是用 AspectJ 编译时织入),但在 Spring 生态中这是最常见的做法,面试也常考,建议掌握。
三、需求溯源推演:这个模块最初的需求是什么?
让我们尝试还原这个模块最初的产品需求文档。
场景假设:2019 年,一个 50 人的软件外包公司,老板接了一个政务系统的单子。甲方要求:"系统要有登录功能,不同的人看到不同的菜单,领导能看到所有人的数据,普通员工只能看自己的。"
第一版需求可能非常简单------"做一个登录页面,用户有角色,角色能分配菜单权限。"这就是 RBAC 的 MVP。产品经理画一个原型:用户管理页面、角色管理页面、菜单管理页面,三个页面互相配合就能完成权限配置。
第二版需求来自甲方领导:"张经理只能看到财务部的数据,不能看到技术部的。"于是数据权限诞生了------从"能不能看这个页面"升级为"能看到这个页面里的哪些数据"。5 级数据权限不是一次性设计出来的,而是在多个项目中反复被甲方提出后,逐步沉淀为标准配置。
第三版需求来自 SaaS 化尝试:"我们想把这套系统卖给 10 家客户,每家客户的数据要隔离。"多租户需求倒逼认证层增加租户识别能力------每次请求都要知道"当前用户属于哪个租户"。
第四版需求来自互联网化:"客户不想记密码,用微信扫码登录行不行?"于是社交登录诞生了------JustAuth 库被引入,支持微信、钉钉、支付宝等十几种社交平台。
核心洞察:认证与权限模块的需求不是一次性设计出来的,而是在"企业内部后台→多租户 SaaS→互联网化"的演进过程中逐步叠加的。RuoYi 的架构设计反映了一个典型的中国企业软件成长路径。
四、竞品对标分析:同类项目怎么做的?
4.1 认证方案对比
| 项目 | 认证方案 | Token 格式 | 社交登录 | SSO |
|---|---|---|---|---|
| RuoYi-Vue-Pro | Spring Security + 自研 OAuth2 Token | UUID(MySQL+Redis) | JustAuth(微信/钉钉等) | 单体版不支持,Cloud 版支持 |
| JeecgBoot | Shiro + JWT | JWT(有状态,Redis 存黑名单) | 集成 CAS 和 OAuth2 | 支持 CAS SSO |
| Pig | Spring Authorization Server | 标准 OAuth2 Token | 支持 | 原生支持 |
| Guns | JWT | JWT | 基础支持 | 不支持 |
| SpringBlade | Spring Security + JWT | JWT | 支持 | 支持 |
RuoYi 的优势在于实现简洁、学习成本低、Token 可随时失效(支持踢人)。劣势在于缺乏标准化------如果客户需要"作为第三方应用接入 RuoYi 的认证",自研方案需要大量改造。Pig 的 Spring Authorization Server 方案在标准化方面最强,但学习曲线也最陡。
4.2 数据权限对比
| 项目 | 数据权限粒度 | 实现方式 |
|---|---|---|
| RuoYi-Vue-Pro | 5 级(全部/指定部门/本部门/本部门及下级/仅本人) | MyBatis 拦截器 + JSqlParser |
| JeecgBoot | 3 级(全部/本部门/本人) | MyBatis 拦截器 |
| Pig | 无内置数据权限 | 依赖业务代码自行实现 |
| Guns | 3 级 | MyBatis 拦截器 |
| SpringBlade | 支持自定义规则 | MyBatis 拦截器 |
划重点!!! RuoYi 在数据权限方面是同类项目中最完善的。5 级粒度 + SQL 自动改写的组合,在开源后台中属于领先水平。
4.3 优势与劣势总结
优势:数据权限粒度最细(5 级)、社交登录支持最全(JustAuth 集成十几种平台)、权限缓存设计完善(多层缓存 + 自动失效)、代码可读性好(权限标识三段式命名统一)。
劣势:认证方案非标准化(不利于 SSO 和开放平台对接)、缺少 ABAC 能力(无法基于业务属性做细粒度控制)、OAuth2 实现是"简化版"(不完全符合 RFC 6749 规范)、权限模型相对传统(不支持动态权限、不支持权限继承链)。
五、核心业务流程
5.1 管理员登录流程
先看登录这个最核心的流程,我画了一个时序图:

5.2 请求鉴权流程(每次 API 调用都走这个链路)
这个流程图建议收藏,面试也常问:

5.3 OAuth2 第三方授权流程(授权码模式)

六、数据模型解读:20 张表,分五组搞定
认证与权限模块涉及 20 张数据库表,看着多,其实分五组就清楚了。
6.1 RBAC 核心四表
system_users ──< system_user_role >── system_role ──< system_role_menu >── system_menu
system_users(用户表):核心字段包括 username(唯一索引)、password(BCrypt 加密,2a 前缀)、dept_id(外键关联部门)、mobile/email(各自唯一索引)。
踩坑提醒 :status 字段------0 表示正常、1 表示禁用。禁用用户时系统会立即撤销其所有 AccessToken,这个设计很关键。
system_role(角色表):关键字段是 code(权限标识,如 super_admin)、data_scope(数据权限级别 1-5)、data_scope_dept_ids(当 data_scope=2 时,存储 JSON 格式的部门 ID 数组)、type(1=内置角色不可修改,2=自定义角色)。
system_menu(菜单表):三种类型------DIR(目录)、MENU(页面)、BUTTON(按钮)。permission 字段存储权限标识符(如 system:user:create),只有 BUTTON 类型才需要设置。
注意:这张表没有 tenant_id 字段------菜单是全局的,租户能看到的菜单范围通过租户套餐(system_tenant_package.menu_ids)来控制。
system_role_menu 和 system_user_role 是两张关联表,分别维护"角色-菜单"和"用户-角色"的多对多关系。
6.2 OAuth2 五表
system_oauth2_client(客户端注册)、system_oauth2_access_token(访问令牌)、system_oauth2_refresh_token(刷新令牌)、system_oauth2_code(授权码)、system_oauth2_approve(用户授权记录)。
划重点:AccessToken 同时存储在 MySQL 和 Redis 中------MySQL 做持久化,Redis 做高速缓存。查询时先查 Redis,miss 后回查 MySQL 并回填 Redis。这种双写策略保证了性能和可靠性的平衡。默认客户端 client_id=default,AccessToken 有效期 30 分钟,RefreshToken 有效期 30 天。
6.3 社交登录三表
system_social_client(社交平台配置)、system_social_user(社交用户信息)、system_social_user_bind(社交账号与系统账号的绑定关系)。通过这三张表,系统可以支持"微信登录后自动关联已有账号"或"钉钉登录后自动创建新账号"等场景。
6.4 审计日志两表
system_login_log(登录日志)和 system_operate_log(操作日志)。这两张表是安全审计的基础------当发生数据泄露时,可以通过登录日志追溯可疑账号。
七、产品设计亮点与槽点
亮点
亮点一:@PermitAll 自动发现机制。 框架在启动时扫描所有 HandlerMethod,自动将标注了 @PermitAll 的接口加入白名单。开发者只需要在 Controller 方法上加一个注解,不需要手动维护 URL 白名单列表。相比之下,JeecgBoot 需要手动在配置文件中列出所有公开接口。
亮点二:@ss SpEL 表达式的统一入口。 所有权限检查都通过 @PreAuthorize("@ss.hasPermission('xxx')") 完成,ss 是一个 Spring Bean 的名字。这个设计让权限检查的调用方式高度统一------全项目 250+ 个文件用完全相同的模式,新开发者一看就懂。
亮点三:数据权限的"无侵入"设计。 DeptDataPermissionRule 通过 MyBatis 拦截器在 SQL 层面自动追加过滤条件,业务代码完全不需要关心数据权限。开发者写 SELECT * FROM order WHERE ... 时,拦截器会自动变成 SELECT * FROM order WHERE ... AND dept_id IN (1,2,3)。这种设计避免了"每个查询都要手动加权限条件"的重复劳动,也杜绝了"忘记加条件导致数据泄露"的风险。
亮点四:TransmittableThreadLocal 安全上下文传播。 Spring Security 默认用 ThreadLocal 存储安全上下文,但异步线程(@Async、线程池)中会丢失。RuoYi 用阿里巴巴的 TransmittableThreadLocal 替换了默认的 ThreadLocal,确保异步场景下安全上下文能正确传播。这是一个容易被忽视但很关键的细节。
槽点
槽点一:Token 双写的一致性问题。 AccessToken 同时存在 MySQL 和 Redis 中,虽然有"Redis miss 后回查 MySQL"的兜底机制,但如果 Redis 故障恢复后数据丢失,高并发场景下可能导致大量请求穿透到 MySQL。
槽点二:Mock 登录的安全风险。 开发模式下支持 Mock 登录(Token 格式为 {mockSecret}{userId}),虽然只在 mockEnable=true 时生效,但如果生产环境误配置开启了这个选项,任何人都可以通过构造 Token 来模拟任意用户登录。
踩坑提醒:建议在生产环境的配置检查中增加对 mockEnable 参数的强制校验,启动时如果是生产 profile 且 mockEnable=true,直接拒绝启动。
槽点三:OAuth2 实现的"半成品"感。 RuoYi 实现了 OAuth2 的五种授权模式,但实现细节并不完全符合 RFC 6749 规范。比如授权码的有效期硬编码为 5 分钟、Token 没有签名验证、缺少 token introspection endpoint 的标准实现。如果企业需要将这些接口暴露给外部第三方应用,需要额外的安全加固。
八、发散性思考
8.1 这个模块还能做什么?
细粒度操作审计:目前的操作日志是"事后记录"模式,可以增加实时风控能力------比如检测到"某用户 5 分钟内尝试导出 10 次数据"时自动触发告警或临时封禁。
动态权限引擎:当前 RBAC 是静态的(管理员配置好角色和权限后,用户权限不会自动变化)。可以增加基于时间、地点、设备等条件的动态权限------比如"非工作时间禁止访问财务模块"、"从外部 IP 登录时自动降级权限"。
权限继承与委托:增加"权限委托"功能------经理出差时可以临时把审批权委托给副经理,回来后自动收回。这在中国企业的 OA 场景中非常常见。
8.2 如果让我重新设计
我会考虑用 JWT + Redis 黑名单 的混合方案替代纯 UUID Token。JWT 的优势在于:无状态验证(不需要每次都查 Redis)、可以携带用户信息(减少数据库查询)、标准化程度高(利于 SSO 对接)。同时用 Redis 维护一个短期黑名单(只缓存被主动注销的 Token),解决 JWT 无法主动失效的问题。
数据权限方面,我会考虑引入 ABAC(基于属性的访问控制) 作为 RBAC 的补充。有些场景下,数据权限不应该只看"你在哪个部门",还要看"数据是什么类型"、"当前是什么时间"、"你从哪里访问"。ABAC 可以更灵活地表达这些策略。
8.3 技术/设计思路的迁移价值
SQL 拦截器的数据权限模式可以迁移到任何需要行级数据隔离的场景------多租户 SaaS、数据脱敏、合规审计。核心思想是"在 SQL 层面统一处理,业务代码不感知"。
@PermitAll 自动发现机制的设计思路可以迁移到 API 网关的白名单管理------通过注解自动同步公开接口列表,避免手动维护导致的配置遗漏。
依赖反转的权限解耦模式(框架定义 SPI 接口 → 业务模块实现)可以迁移到任何需要"框架与业务解耦"的场景。核心价值是:框架层不需要知道业务细节,业务层不需要关心框架实现,两者通过接口契约协作。
九、关键代码导读(最值得读的 5 个文件)
老规矩,直接上干货:
1. TokenAuthenticationFilter.java
路径:yudao-framework/yudao-spring-boot-starter-security/src/main/java/.../filter/TokenAuthenticationFilter.java
为什么值得读:这是整个认证系统的"咽喉要道"------每个 HTTP 请求都必须经过这个 Filter。它负责从请求中提取 Token、验证 Token 有效性、构建 LoginUser 对象并设置到 SecurityContext 中。特别注意它对 userType 的校验逻辑------admin-api 和 app-api 的 Token 不能混用,这是一个安全设计。
2. PermissionServiceImpl.java
路径:yudao-module-system/src/main/java/.../service/permission/PermissionServiceImpl.java
为什么值得读:这是权限系统的"中枢神经"------它协调用户、角色、菜单三者之间的关系,同时负责数据权限的计算。重点看 hasAnyPermissions() 方法和 getDeptDataPermission() 方法。代码中大量使用了 getSelf() 模式来确保缓存注解生效,是一个值得学习的 Spring 技巧。
3. DeptDataPermissionRule.java
路径:yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/.../rule/dept/DeptDataPermissionRule.java
为什么值得读:这是数据权限的核心实现------它通过 JSqlParser 在 SQL AST 层面自动追加 WHERE 条件。读懂这个文件,就理解了"为什么业务代码不需要关心数据权限"。重点看它如何处理五种 DataScope 的不同逻辑。
4. AdminAuthServiceImpl.java
路径:yudao-module-system/src/main/java/.../service/auth/AdminAuthServiceImpl.java
为什么值得读:这是登录/登出/注册/重置密码等所有认证操作的业务入口。重点看 login() 方法的完整链路(验证码校验→密码校验→社交绑定→创建 Token→记录日志)。
5. YudaoWebSecurityConfigurerAdapter.java
路径:yudao-framework/yudao-spring-boot-starter-security/src/main/java/.../config/YudaoWebSecurityConfigurerAdapter.java
为什么值得读:这是 Spring Security 的核心配置类------它定义了 Filter Chain 的结构、URL 授权规则、会话策略。重点看它如何自动扫描 @PermitAll 注解来构建白名单,以及如何通过 AuthorizeRequestsCustomizer 让每个模块贡献自己的 URL 规则。
十、写在最后
认证与权限模块是每一个企业后台系统的"命门"。RuoYi-Vue-Pro 在这方面的设计,既有"够用就好"的务实,也有值得商榷的妥协。
从产品经理的角度看,这个模块最大的启示是:权限不是一次性设计出来的,而是随着业务场景的演进而逐步叠加的。 从最简单的"登录+菜单",到数据权限、多租户、社交登录,每一次扩展都是对原有架构的考验。RuoYi 通过"三层权限模型 + SQL 拦截器 + 依赖反转"的组合拳,在保持代码可读性的同时,实现了相当不错的扩展性。
明天我们进入用户与组织模块,看看用户、部门、岗位这些"组织细胞"是怎么管理的。
系列文章导航
- 框架层(yudao-framework)------ 已完成
- 认证与权限(auth/security/permission)------ 本篇
- 用户与组织(user/dept/role)------ 下一篇
- 多租户(tenant)
- ... 更多模块持续更新中
觉得有用的话,点个赞 + 收藏 + 关注支持一下呗~ 你们的支持是我持续拆解的动力!有什么问题欢迎评论区交流,我会逐条回复。