搜索 K
Appearance
Appearance
这一篇是安全鉴权设计文档的入口。它先把鉴权模块的整体分层与「核心契约 + 双实现」骨架讲清楚,再分别下钻到三个关键设计决策:AuthProvider 为什么强制一元化、@Public 与白名单为什么要做三层聚合(★ 本篇重点)、以及启动期落库为什么挂在应用 Ready 时刻。
如果你只想尽快把鉴权跑起来、复制可用的配置与代码,请直接看使用指南:安全鉴权 · 接入指南。
鉴权模块刻意把「契约」与「实现」拆成不同的 Maven 模块。核心包 forge-auth 只定义协议、模型、拦截链与启动钩子,绝不绑定任何具体的令牌技术;真正签发与校验令牌的能力,由两个互斥的实现包二选一提供。
这套分层带来一个直接好处:业务代码、拦截链、RBAC 模型完全感知不到底层用的是 JWT 还是 Sa-Token。切换令牌方案,等价于替换一个 Maven 依赖坐标,应用层零改动。
两个实现包都实现了同一个 AuthProvider 契约,但内部哲学不同。
| 维度 | SpringSecurityAuthProvider | SaTokenAuthProvider |
|---|---|---|
| 底层技术 | JJWT 无状态 JWT | Sa-Token 1.39.0 |
| 令牌签发 | ForgeAuthRuntime.codec().sign(user) | StpUtil.login 后取 StpUtil.getTokenValue() |
| 令牌校验 | codec().parse(token) 回填声明 | 取 loginId 后 ForgeLoginUsers.load(userId) 回源 DB |
| 用户信息来源 | JWT 自带声明(username/nickname/superAdmin) | 回源数据库重建 LoginUser |
| 主动失效 | 空实现(纯无状态无法失效) | StpUtil.logoutByTokenValue(token) |
| 适用场景 | 轻量、纯无状态、原生 Spring 集成 | 需要会话/失效/更多功能注解 |
差异的根因是「用户信息放在哪」:Spring 方案把信息塞进 JWT 声明里随令牌漂移,校验时无需回库;Sa-Token 方案只在令牌里存一个 userId,校验时回源数据库重建 LoginUser,因此不依赖令牌内部格式。这条分歧线,决定了它们在「主动失效」「无状态纯度」上的不同取舍。详见 AuthProvider 设计。
把上面的静态结构放到一次真实请求里看,链路是这样的:
这里有两个设计意图值得点出来:其一,@Public 检查发生在令牌提取之前,免登录端点不会因为缺令牌而被误拦;其二,登录上下文走 ThreadLocal,请求收尾时必须清理,避免线程复用导致的身份串号。
ForgeAuthProvider.get() 通过 Spider.getAllExtensions(AuthProvider.class) 拿到所有实现,并强制「恰好一个」——零实现或多实现都会在启动期直接抛 IllegalStateException。
这是典型的启动期 fail-fast:与其让两套互不兼容的令牌格式在运行时悄悄打架,不如在启动那一刻就让应用起不来。完整动机与编解码下沉设计见 AuthProvider 设计。
放行规则不是单一来源,而是三层来源聚合到同一份「免校验清单」里:
为什么要分三层?因为「谁该被放行」这件事,天然有三类不同的主人:框架自己必须放行登录与文档端点(硬编码),运维需要按环境临时加白名单(配置),各业务模块需要在代码侧声明自己的公开路径(SPI 贡献)。再叠加一个 @Public 注解,让单个端点级别的免登录贴在方法旁边、就近可见、最不容易遗忘。这一节是设计篇的重点,三层如何收敛、无 SPI 实现时为何零副作用、@Public 与白名单的优先级,全部展开在 @Public 与白名单聚合。
ForgeAuthMetaLoader 监听 ApplicationReadyEvent 而非 ContextRefreshedEvent 来播种默认管理员(admin/admin)、SUPER_ADMIN 角色与绑定关系。
为什么是 Ready 而不是 Refreshed
ContextRefreshedEvent 触发时容器虽已刷新,但数据源、事务、ORM 不保证完全可用;ApplicationReadyEvent 触发时整个应用已 Ready,可以放心调用 Models.origin(AuthUser.class).save() 这类落库操作。同时初始化逻辑是幂等的——存在则跳过,支持反复重启不重复创建。
落库时序的更多细节(幂等检查顺序、为何创建后重查主键、典型日志)与 RBAC 模型设计,参见各对应分篇。
| 选项 | 选择 | 理由 |
|---|---|---|
| 多实现并存 vs 强制唯一 | 强制唯一,fail-fast | 避免令牌格式冲突与上下文混乱,确定性优先 |
| 核心绑定具体令牌技术 vs 契约下沉 | 契约下沉到 forge-auth | 切换 JWT/Sa-Token 仅换依赖,应用层零改动 |
| 单一放行来源 vs 三层聚合 | 硬编码 + 配置 + SPI 三层聚合 | 框架、运维、各业务模块各自管好自己的公开端点 |
ContextRefreshedEvent vs ApplicationReadyEvent | ApplicationReadyEvent | 确保 DB/ORM 完全就绪后再落库,时序清晰 |
| JWT 声明带用户信息 vs 回源 DB | 双实现各取一种 | 无状态纯度(JWT)与可失效/可控(Sa-Token)的不同诉求 |
| 引入整套 Security vs 仅引 crypto | 仅引 spring-security-crypto | 密码加密能力够用即可,依赖轻量 |
当前能力边界
按钮级授权(@Action)、基于权限码的细粒度鉴权尚未实现,模块当前聚焦在登录认证与放行控制;superAdmin 标记目前用于身份标识,权限绕过的检查逻辑为预留。设计时不要把它当成已落地的 RBAC 全功能集来引用。