JWT 还是 Token+缓存?微服务鉴权方案选型详解
作者:KAKAKA2026.11.07 03:17浏览量:0简介:鉴权是微服务体系的第一道安全关口。本文系统拆解 JWT 无状态令牌与 Token+分布式缓存有状态会话两大主流方案,从登录签发、网关校验、报文注入到下游消费的完整链路展开,对比可注销性、权限时效、性能开销与容灾成本,并给出典型报文结构与场景化选型建议,帮助你在架构设计时做出合适决策。
在微服务架构下,系统被拆分为多个独立服务,用户的一次请求往往要跨越网关、用户服务、业务服务等多个节点。“用户是谁、是否登录、有没有权限”这三个问题必须在入口处统一回答,否则每个服务都各自实现一遍鉴权,既重复又容易出漏洞。业界的主流做法是在 API 网关集中鉴权,而网关背后有两种经典流派:
- JWT(JSON Web Token):自包含、无状态,签名校验通过即放行;
- Token + 分布式缓存:签发一个高熵随机串作为“钥匙”,服务端在缓存中维护会话状态,每次请求查询校验。
两者没有绝对高下,差异的本质是“状态放在令牌里”还是“状态放在服务端”。下面分别拆解。
一、JWT:把状态写进令牌
1.1 结构与原理
JWT 由三段 Base64Url 编码的文本用点号拼接而成:
base64(header).base64(payload).base64(signature)
示例(载荷脱敏):
// header{"alg": "HS256", "typ": "JWT"}// payload{"sub": "u-88123", "iss": "auth-svc", "iat": 1733712000, "exp": 1733719200}
签名部分是对前两段做 HMAC-SHA256(或 RSA/ECDSA 私钥签名)的结果。网关拿到令牌后本地验签,签名正确且未过期,即信任载荷中的身份声明,全程不需要查询任何存储。
1.2 优点
- 无状态、易扩展:任意网关实例都能独立校验,天然适合多集群、跨地域部署;
- 自包含:身份、角色、租户等信息随令牌流动,下游服务解码即用;
- 标准化:协议成熟、实现广泛,适合开放 API 与跨组织对接。
1.3 短板
- 无法主动吊销。令牌一旦签发,在过期时间之前始终有效。用户改密码、被封禁、主动退出,服务端都“收不回”已发出的令牌——除非引入黑名单缓存,而这就重新回到了有状态方案,等于叠加两套方案的复杂度。
- 权限变更不实时。载荷中的角色与权限是签发时刻的“快照”,管理员中途调整权限,要等旧令牌过期才生效。
- 载荷明文可读。Base64 只是编码不是加密,任何人拿到令牌都能读出其中的声明,敏感数据绝不能放。
- 体积偏大。数百字节到数 KB 的字符串每次请求都要携带,高频调用下带宽与解析开销可观。
- 续签与密钥管理复杂。通常需要引入 access/refresh 双令牌机制;对称密钥泄露或算法配置不当,都会带来严重的安全风险。
二、Token + 分布式缓存:把状态留在服务端
这是另一条经典路线,也是不少中大型平台内部广泛使用的方案。整体角色包括:API 网关(总入口)、用户/登录服务(签发与校验)、分布式缓存(会话存储)、若干业务微服务。
2.1 登录流程
- 登录接口在网关侧配置为白名单,不携带令牌即可访问;
- 用户服务校验账号口令通过后,用密码学安全随机数生成一个不透明令牌(如 32 字节随机数的十六进制表示,熵不低于 256 bit);
- 将本次登录上下文(用户 ID、登录时间、客户端 IP、设备指纹、地域等)连同用户信息打包成 JSON,作为 value 写入缓存,key 即该随机令牌,并设置 TTL(例如 2 小时);
- 接口把令牌返回给前端,同时下发
Set-Cookie: access_token_id=xxxx(HttpOnly、Secure)。
示意伪代码:
def login(username, password, ctx):user = verify_credentials(username, password) # 校验账号口令token = secure_random_hex(32) # 256-bit 不透明令牌session = {"userID": user.id,"ctime": now(),"clientIp": ctx.ip,"region": ctx.region,"device": ctx.ua_hash,}cache.set("session:" + token, json.dumps(session), ttl=7200)resp.set_cookie("access_token_id", token, http_only=True, secure=True)return {"expires_in": 7200}
2.2 鉴权流程
此后前端每次调用,携带 access_token_id 请求头或 Cookie。网关的处理链路如下:
- 从报文中提取令牌(优先取 header,其次取 cookie);
- 携带令牌及当前请求上下文(IP、地域等风控要素)调用用户服务的校验接口,后者查询缓存获取会话;命中且风控校验通过(例如异地登录触发告警),返回用户信息;
- 网关将用户信息覆盖写入 POST 请求体的
userInfo字段,同时把网关公共信息(出口节点 IP 列表、环境标识等)补充到commons字段,再转发给业务微服务; - 业务服务只需读取报文中的
userInfo字段:字段存在即已登录,字段内容即当前用户——下游编码极其简单。
def on_request(ctx):token = ctx.header.get("access_token_id") or ctx.cookie.get("access_token_id")if not token:return abort(401)user = user_service.introspect(token, ctx.ip, ctx.region)if user is None:return abort(401) # 未登录 / 会话过期 / 风控拦截ctx.body.pop("userInfo", None) # 剥离客户端伪造字段,防止串号ctx.body["userInfo"] = userctx.body["commons"] = {"apiGw": ctx.gateway_nodes, "env": ctx.env}forward(ctx)
一个典型的业务请求体结构(数据脱敏)如下:
{"userInfo": {"userID": "uxM11-wx177588","ip": ["179.8.16.66", "179.8.16.1", "166.19.10.1"],"ctime": "2024-12-09 09:56:55"},"commons": {"apiGw": ["10.0.96.17", "10.0.96.18", "10.0.96.19"],"env": {}},"data": {"typeName": "Person","mapper": {"nodeType": "Actor", "nodeLabel": "Person"}}}
2.3 优点
- 可注销、可管控:删除缓存中的 key,令牌立即失效;封号、踢人、强制下线都是 O(1) 操作;
- 权限实时生效:会话数据每次从服务端读取,角色变更即时反映;
- 天然防串号(横向越权):用户身份由网关从可信会话注入,客户端即便在报文里伪造
userInfo或userID,也会被网关剥离覆盖,越权风险从入口被堵死; - 令牌本身零信息:随机串即使被窃取,也泄露不了任何身份内容,配合 IP 绑定与风控可进一步降低被冒用的影响;
- 多端会话可视:以“用户 → 令牌列表”为索引,可以轻松实现“登录设备管理、逐端下线”这类体验型功能。
2.4 代价
- 每个请求多一次缓存查询,链路增加一跳(可用网关本地一级缓存、短 TTL 多级缓存摊薄);
- 强依赖缓存可用性:缓存集群故障意味着全站无法鉴权,必须按核心链路标准做高可用与降级预案;
- 会话集中存储带来容量与热点压力,需要提前做容量规划。
三、逐项对比
| 对比维度 | JWT | Token + 分布式缓存 |
|---|---|---|
| 校验位置 | 网关本地验签 | 集中查询缓存 |
| 主动失效 | 困难,需额外黑名单 | 删除 key 即时失效 |
| 权限变更时效 | 需等令牌过期 | 即时生效 |
| 令牌信息量 | 自包含、明文可读 | 随机串、无信息 |
| 每请求开销 | CPU 验签 + 大载荷传输 | 一次缓存查询 |
| 基础设施依赖 | 无(仅需分发密钥) | 强依赖缓存高可用 |
| 多端会话管理 | 弱 | 强,可枚举、可踢下线 |
| 典型场景 | 开放 API、跨域、无状态扩展 | 内部系统、强管控与风控 |
四、为什么不少云平台开放接口偏爱 POST + 统一报文
观察多家头部云厂商的开放 API,会发现大量接口采用 POST + 统一 JSON 报文,而非严格的 REST 风格。这并非“不会用 REST”,而是工程上的综合权衡,其中与鉴权直接相关的因素包括:
- 身份注入需要结构化载体。网关鉴权后要把
userInfo、commons写进请求体,统一的 POST JSON 报文天然适合“公共参数 + 业务参数”的包裹结构; - 避免敏感信息上 URL。GET 的查询串会进入访问日志、代理日志与浏览器历史,令牌与业务参数放在请求体里更安全;
- 统一收口便于审计与风控。所有请求走同一报文结构,网关可以无差别地做签名校验、限流、重放检测与全链路日志。
当然,REST 在资源型操作、HTTP 缓存语义上仍有不可替代的价值,两者是场景选择而非对错之分。
五、选型建议
给出一个可操作的决策路径:
- 内部微服务、后台系统、需要强会话控制(踢人即时生效、风控、多端管理)→ 优先 Token + 分布式缓存;
- 对外开放 API、跨组织对接、多地域无状态扩展 → 优先 JWT(短有效期 + refresh token 轮换,必要时配合吊销名单);
- 两者兼有的平台 → 常见做法是混合架构:对外签发 JWT,网关验证后换取内部会话再转发,做到“外部无状态、内部强管控”;
- 也可采用“JWT + 缓存黑名单”的折中方案:令牌自包含,吊销时把唯一标识写入黑名单,代价是重新引入集中状态。
六、工程落地要点
无论选哪条路线,以下几件事必须做到位:
- 令牌熵与传输安全:随机令牌必须由密码学安全随机数生成,长度不低于 128 bit;浏览器场景优先使用 HttpOnly + Secure + SameSite 的 Cookie,降低被脚本窃取的风险;App 与服务端调用则更适合同名请求头方式,且全程强制 HTTPS;
- TTL 与滑动续期:会话有效期不宜过长(常见 30 分钟至 2 小时),每次合法访问刷新过期时间,兼顾安全与体验;
- 注入字段必须覆盖而非合并:网关注入
userInfo前先删除客户端传入的同名字段,这是防越权的关键细节,也是这类方案容易被忽视的坑; - 缓存高可用:按生产核心组件标准部署缓存(多副本、故障自动切换),网关侧再加一层 5~10 秒的本地缓存兜底,避免缓存抖动被放大为全站鉴权故障;
- 审计与限流:以令牌为维度记录调用日志,对高频、异地等异常调用模式触发风控复核。
七、小结
JWT 与 Token + 分布式缓存的争论,本质是“信任令牌本身”还是“信任服务端状态”。JWT 换来的是无状态的简洁与横向扩展能力,代价是失去对已签发令牌的控制力;Token + 缓存换来的是随时可回收的强管控与更简单的下游编码,代价是对缓存基础设施的依赖与每次请求的一跳查询开销。对大多数企业内部系统而言,会话可控性的价值高于一次本地验签的节省;而对开放、跨域、无状态场景,JWT 更为契合。理解两者的能力边界,按场景组合使用,比争论“哪个更好”更有意义。

登录后可评论,请前往 登录 或 注册