Auth 架构设计
多应用、多终端的认证鉴权,核心不是给每个系统都写一套登录逻辑,而是把身份可信链路收口成三件事:身份中心负责认证,网关负责鉴权,JWT 作为无状态凭证在系统间传递。本文把这三件事放到统一账号系统、Spring Cloud Gateway、Spring Security 和 Redis 的架构里讲清楚。
一、先分清三个词
很多人把 JWT、认证、鉴权混在一起,但它们处在不同层面:
| 概念 | 回答的问题 | 落地位置 | 一句话解释 |
|---|---|---|---|
| Authentication 认证 | 你是谁 | 认证中心 / 登录流程 | 验证用户身份是否合法 |
| Authorization 鉴权 | 你能做什么 | 网关 / 资源服务 | 判断用户是否有权访问某个应用或接口 |
| JWT | 用什么证明身份 | 请求头Authorization: Bearer | 一种无状态、可验签的身份凭证格式 |
可以这样比喻:
- 认证是小区保安确认“你是不是业主”;
- 鉴权是保安继续确认“你只能进自己单元,不能进付费健身房”;
- JWT是那张带签名、带身份信息和有效期的电子业主卡。
还有一个容易混淆的关系:JWT 是 Token 格式,OAuth2 是授权框架。实际项目中通常是 OAuth2 完成授权流程,授权成功后返回一个 JWT 格式的access_token。
二、先定义架构要解决什么
在 Mall 这类多应用电商系统里,认证鉴权要同时解决四类问题:
| 痛点 | 典型表现 |
|---|---|
| 登录状态不共享 | App 登录后,进入官网还要重新输密码 |
| 权限边界混乱 | 商品运营能删订单,区域店员能看全国报表 |
| 重复造轮子 | 每个微服务都写一遍登录、验 Token、查权限 |
| 性能与安全难以兼顾 | 查库鉴权打满数据库,JWT 又存在主动登出难题 |
所以合理的架构目标不是“功能能跑”,而是:
- 用户一次登录,多个应用、多个终端都能识别身份;
- 不同应用、不同角色访问不同权限,权限变更能秒级生效;
- 业务服务不重复实现认证鉴权,只专注业务;
- 高并发下鉴权链路不能被数据库拖垮。
三、统一认证:授权码模式的 SSO 链路
统一账号系统通常称为 UAA(Unified Authentication Authority)。多应用场景推荐使用 OAuth2.0 授权码模式,而不是让前端直接拿密码换 Token。
核心原因是授权码模式把敏感信息留在后端:浏览器只能拿到一次性的code,真正的access_token必须由业务后端带着client_secret换取。
这个流程里有三类凭证,作用完全不同:
| 凭证 | 生命周期 | 存储位置 | 核心作用 |
|---|---|---|---|
authorization_code | 5-10 分钟,一次性 | 重定向 URL 中短暂出现 | 把“用户已授权”安全传给业务后端 |
access_token | 通常 1-2 小时 | 前端 Cookie / Storage,请求头传递 | 访问业务资源的真正通行证 |
refresh_token | 数天到数周 | 后端 HttpOnly Cookie / DB | 在 access_token 过期后静默续期 |
设计要点:
authorization_code必须配合client_secret才能换 Token,浏览器截获也无法直接使用;refresh_token不暴露给前端 JavaScript,否则长期凭证就失去了保护;- JWT 只放
userId、username、roleType、exp等非敏感字段,不放手机号、邮箱、密码和完整权限列表。
四、统一鉴权:认证之后还要划清权限边界
认证通过只代表“这是个合法用户”,不代表“他有权访问这个系统”。多应用场景下,鉴权需要两个维度同时成立:
当前用户 + 当前系统 + 当前终端 -> 是否拥有当前 URL 权限4.1 网关里的三层 Filter
统一鉴权通常收口到 Spring Cloud Gateway。一个请求进入网关后,可以按三层责任链处理:
| 层级 | 动作 | 实现 |
|---|---|---|
| 第一层:身份认证 | 校验 JWT 是否合法、是否过期、是否在黑名单 | JwtUtil.verify(token) |
| 第二层:权限加载 | 根据用户、系统、终端读取权限集合 | redis.get("user:perms:" + userId + ":" + systemCode) |
| 第三层:访问控制 | 匹配当前 URL 和请求方法是否在权限集合中 | AntPathMatcher+system_code |
4.2 用 system_code 做系统间隔离
只做 RBAC 不够。同一个用户在不同系统里,权限可能完全不同。所以在权限模型中增加system_code字段:
user_id + role_id + permission_code | +-- system_code 例如 product / order / inventory / admin +-- terminal_type 例如 pc / app / miniprogram这样就能表达:
- 华东区店员只能查看本店销售数据;
- 总部运营可以查看全国经营报表;
- 抢购活动配置只允许特定运营角色操作;
- 同一用户在 App 能看完整订单,在小程序只能看简化订单。
4.3 权限动态刷新
权限不能每次请求都查数据库。正确做法是登录后写入 Redis,权限变更时通过消息队列广播刷新:
收益很直观:鉴权从查库的 80ms 级别降到查 Redis 的 8ms 级别,权限变更从“重启服务”变成秒级生效。
五、为什么 JWT 比直接传用户名密码更安全
JWT 的安全不是“加密了所以安全”,而来自它带来的三个变化:
5.1 不再重复传输密码
传统方式下,用户每次访问都可能携带账号密码或服务端 Session ID。JWT 只在登录时交换一次用户名密码,后续请求使用经过签名的 Token,密码不会反复暴露。
5.2 签名保证不可篡改
JWT 由Header.Payload.Signature三部分组成。签名端使用后端私密密钥计算,任何对 payload 的修改都会导致验签失败。即使攻击者拿到 Token,也无法把普通用户改成管理员。
5.3 生命周期可控
项目里可以设置access_token2 小时、refresh_token7 天。即使 Token 泄露,攻击窗口也被限制在有限时间内。
但 JWT 并不是完美方案,生产环境必须补上三块:
- Token 黑名单:JWT 天然无法主动失效,登出时把
jti + 剩余有效期写入 Redis,网关先查黑名单再验签; - HTTPS + HttpOnly:Token 传输必须加密,浏览器端优先 HttpOnly Cookie,降低 XSS 窃取风险;
- 轻量 payload:完整权限不放 Token,只在 Token 中放最小身份标识,避免 Token 过大和敏感信息暴露。
六、推荐落地架构
综合上面的问题,推荐采用:
Spring Cloud Gateway + Spring Security + JWT + Redis + MQ| 组件 | 职责 |
|---|---|
| UAA 认证中心 | 统一登录、OAuth2 授权码签发、Token 签发与刷新 |
| Spring Cloud Gateway | 总入口,统一验签、加载权限、做访问控制 |
| JWT | 无状态身份凭证,支持多应用和多终端传递 |
| Redis | 缓存权限、Token 黑名单、短时授权码 |
| MQ | 广播权限变更,触发 Redis 刷新 |
| Nacos | 管理 client 白名单、密钥、路由配置 |
整体可以拆成两条主链路。
链路一:登录与 Token 签发
链路二:业务请求与网关鉴权
权限刷新属于旁路,不参与每次业务请求:
对于单体项目,可以不引入网关,直接用 Spring Security + JWT + RBAC 完成认证与动态鉴权。对于响应式微服务项目,则把 Spring Security 的阻塞 API 换成 WebFlux 版本,使用ReactiveAuthenticationManager、ReactiveUserDetailsService和响应式 Redis 客户端,避免高并发下同步过滤器成为瓶颈。
七、设计原则
最后收敛成五条可以复用的原则:
- 认证与鉴权分离:Token 只证明身份,权限由网关或服务端动态加载;
- 缓存前置:高频权限优先走本地缓存,其次 Redis,最后数据库;
- 系统维度隔离:多系统共用一套账号时,必须用
system_code和terminal_type划清边界; - 最小暴露:JWT payload 只放最小身份信息,不放 PII,不放完整权限;
- 可撤销可追溯:黑名单解决 JWT 无法主动登出的问题,鉴权失败记录日志,配合审计满足合规要求。
小结
Auth 架构设计的核心,不是把 JWT、Gateway、Redis 这些组件堆在一起,而是先分清“认证、鉴权、凭证”三件事,再把它们放到一条清晰的请求链路上:认证中心发证,网关验证并授权,业务服务只处理业务。这套思路可以从单体项目扩展到多应用、多终端的微服务体系。