微服务架构下 鉴权体系 的设计与权衡
最近把手头一个跑了三四年的单体应用往微服务上拆,业务逻辑迁移都还算顺利,结果在鉴权这块卡了好几天。单体的时候一个 Session 走天下,登录之后用户信息往 ThreadLocal 一塞就完事了,拆完之后发现事情完全不对:用户在服务 A 登录了,服务 B 压根不认识你是谁。网上搜了一圈,大部分文章要么无脑吹 JWT 无状态多好多好,要么就是画一堆架构图讲“集中式认证 vs 去中心化认证”,看完还是不知道自己项目里该用哪个。这里记录一下我踩的坑和最后落地的方案,不一定是最优解,但至少是实测跑通的。
单体时代的老办法为什么不灵了
原来单体的鉴权非常朴素:用户登录,服务端生成 Session(后来量大了挪到 Redis),浏览器带个 Cookie,每次请求过来查一下就完事。
拆成微服务之后,这套东西的问题就全暴露了:
- Session 在单个服务的内存里,服务 B 根本读不到服务 A 存的东西
- 就算把 Session 全放 Redis 让大家共享,每个服务都得写一遍“读 Cookie → 查 Redis → 反序列化用户 → 塞上下文”的逻辑,重复代码写到吐
- 前端还要接小程序和 App 的话,Cookie 这套用起来更别扭
所以鉴权逻辑必须收拢到一处,这个大方向没什么争议,争议在于收到哪儿、收到什么程度。
方案一:网关统一鉴权,用户信息透传
最直觉的做法:所有请求先过网关,网关负责认证,通过之后把用户 ID、角色这些信息塞到请求头里转发给下游,下游服务直接从头里读。
我一开始觉得挺完美,直到有一次本地联调,同事直接绕过网关用 curl 调了我一个服务,随手编了个 X-User-Id: 1 的头,就把我登录态模拟出来了。当时才意识到:下游服务是在无条件信任网关的。正常流量走网关没问题,但只要内网有任何一台机器能直连服务端口,伪造个 header 就进去了。内部服务裸奔这事,平时没事,出事就是大事。
网关侧大致是这样(Spring Cloud Gateway):
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
filters:
- RemoveRequestHeader: X-User-Id # 先删掉外部伪造的头
- name: AuthFilter # 再由过滤器验 token、塞入可信的用户头
注意那个 RemoveRequestHeader,这一行很关键,先把外部传进来的同名头删掉,再塞可信的值,不然等于没防。
方案二:JWT,以及它被吹过头的部分
JWT 的卖点是“无状态”:签发之后服务端不用存,任何持有公钥的服务本地验个签就完事,看起来完美。
但真用起来,最经典的问题马上就来了:没法主动失效。
- 用户点了“退出登录”?token 还是有效的,你只能让前端自己删
- 用户改密码了、管理员封号了?token 照样能用到过期
- token 泄露了?什么都做不了,干等它过期
网上常见的补救是搞个黑名单:token 进来先查一遍 Redis。听起来有道理,但你仔细想想——这不就是把状态又存回服务端了吗?每个 token 都要查一次 Redis,和 Session 方案的开销有什么本质区别?还额外搭上签名的 CPU 和更肥的请求头。当然也不是说完全没价值,黑名单里只存“异常注销”的那一小部分,正常过期的不用管,量是可控的。但这时候它就不是“无状态”方案了,别再拿无状态当卖点。
另外提醒一句,JWT 的 payload 只是 Base64 编码,不是加密,谁拿到都能解开看。别把手机号、身份证这种东西塞进去,我真见过往里塞的...
我最后落地的方案
权衡了一圈,最后是这么一套混着用的:
- access token 有效期只给 30 分钟,就算泄露损失也可控;refresh token 给 7 天,只在认证服务里校验,每次刷新会轮换
- 网关统一验签,通过后把用户信息塞 header 转发,配合上面那个 RemoveRequestHeader
- 服务间调用加一道内部签名:服务 A 调服务 B 时带一个用共享密钥算的 HMAC 头,B 先验这个头,不过直接 403。成本很低,但把“内网直连伪造用户”这条路堵死了,比上 mTLS 轻量不少,密钥放配置中心就行,别硬编码进镜像
- 认证服务里维护一个小的 Redis 黑名单,只存主动注销和被封禁 token 的 jti,网关验签时顺带查一下
这样 JWT 就只承担“短期的、失效风险可承担的凭证”这一个角色,不再指望它包打天下。
写在最后
折腾完这一圈,最大的感受是:鉴权这事没有最佳实践,只有你愿意接受哪种代价。
- 想随时踢人下线,就得接受服务端存状态
- 想完全无状态,就得接受 token 在过期前收不回来
- 想内网也安全,就得接受每次调用多算一次签名
网上的文章总喜欢把某个方案捧成银弹,实际项目里基本都是混着用的。我的建议是先想清楚业务对“注销时效”的真实要求——普通信息系统,短 token 过期就过期了,问题不大;支付、后台管理这种敏感场景,别犹豫,上黑名单,或者干脆老老实实 Session + Redis。先有安全要求,再选技术方案,顺序别反了。
评论
还没有评论。
发表评论
提交后评论将经过自动审核,审核通过后公开展示。