2015年JWT(JSON Web Token,一种用于身份验证的令牌格式)刚火起来的时候,技术圈有个说法像咒语一样被重复:服务器不用存任何东西,验证签名就够了。这个承诺太美了——水平扩展时不用操心会话同步,微服务之间不用共享数据库,甚至能省掉一整层缓存。
我信了。AuthShield的前两个版本完全按这个思路设计:签发令牌、丢掉私钥、让下游服务自己验签。直到产品经理在需求文档里写了两个字:「退出登录」。
那一刻我发现,JWT的"无状态"是个有条件的真理——它只在用户不主动退出时成立。
退出登录的物理悖论
JWT的本质是一段带签名的JSON。服务器生成它,客户端保存它,之后每次请求原样带回。服务器验签通过就放行,整个过程不需要查数据库。这个设计有个隐含假设:令牌的生命周期完全由过期时间控制,用户不会提前"杀死"它。
但用户确实会退出。而且用户合理期待:点了退出,账号就该立刻锁死,哪怕令牌还有59分钟才过期。
我最初想的方案很天真:把令牌过期时间设短一点,比如5分钟。但这样用户每5分钟就要重新登录,体验灾难。OAuth 2.0的解法是把令牌拆成两个——访问令牌(Access Token,短期有效)和刷新令牌(Refresh Token,长期有效)。访问令牌过期后用刷新令牌换新的,既保证安全又减少登录频次。
这个方案解决了"频繁登录"问题,但没解决"即时失效"问题。刷新令牌本身也是JWT,如果它被偷了,攻击者能在用户退出后继续刷新访问令牌,直到刷新令牌自己过期——这可能是一周、一个月,甚至永远不过期。
Redis黑名单:给无状态架构打补丁
我最终在AuthShield里加了一层Redis。逻辑很简单:用户退出时,把当前访问令牌的唯一标识(jti,JWT ID)写进Redis,设置过期时间为令牌的剩余有效期。每次请求来时,先查Redis——如果在黑名单里,直接拒绝。
这行代码写进去的时候,我盯着屏幕发了会儿呆。说好的无状态呢?现在每个请求都要走一趟网络IO,Redis挂了全站认证崩,多区域部署还要考虑缓存同步。
但产品经理的需求是合理的。安全团队的要求是合理的。用户期待自己的退出操作能立刻生效,这更是合理的。唯一不合理的是早期JWT宣传里那个被过度简化的"无状态"神话。
Redis黑名单的方案有个明显代价:查询延迟。我测过,本地Redis平均1-2毫秒,跨可用区能到10毫秒以上。对于AuthShield的定位(独立认证微服务,下游可能几十个服务调用),这个开销可以接受。但如果是极高并发的场景,可能需要把黑名单缓存到应用内存,或者用布隆过滤器减少Redis查询次数。
刷新令牌的旋转与 reuse 检测
黑名单只解决了访问令牌的即时失效。刷新令牌更麻烦——它生命周期长,被偷的后果更严重。我的最终方案是"刷新令牌旋转"(Refresh Token Rotation):每次用刷新令牌换取新的访问令牌时,同时颁发新的刷新令牌,旧的那个立即作废。
这个设计有个边缘情况:如果用户在两个设备上同时操作,或者网络延迟导致旧刷新令牌被重发,新令牌已经生成,旧令牌却还在路上。这时候需要"重用检测"(Reuse Detection)——如果服务器收到一个已经作废的刷新令牌,说明它被偷了,立即吊销该用户所有刷新令牌,强制重新登录。
实现这个逻辑时,我意识到刷新令牌必须是有状态的。每个刷新令牌需要记录:用户ID、设备指纹、签发时间、是否已旋转、旋转后的新令牌ID。这些信息存在PostgreSQL里,和JWT的"无状态"承诺彻底告别。
但这也是个务实的妥协。完全无状态的认证只存在于演示代码里。生产环境要处理设备管理、异常检测、用户强制下线、安全事件响应——这些都需要服务器记住点什么。
AuthShield的最终架构
现在的AuthShield有三层状态:Redis存短期黑名单(访问令牌),PostgreSQL存长期令牌元数据(刷新令牌),内存缓存热点黑名单减少Redis压力。访问令牌有效期15分钟,刷新令牌7天,支持配置。
这个架构和2015年那批JWT布道者描述的"服务器无状态"已经相去甚远。但它是诚实的——诚实地承认退出登录是个状态变更操作,诚实地承认安全需要可撤销性,诚实地承认用户控制权比架构纯度更重要。
我后来查资料,发现Auth0、Firebase Auth、AWS Cognito这些主流服务都在用类似的混合方案。JWT的无状态特性被保留在"验证阶段"(任何服务能独立验签),但"生命周期管理"阶段引入了状态层。这不是设计失败,是问题域本身的复杂度决定的。
如果你正在设计认证系统,我的建议是:先问清楚产品需求里有没有"退出登录"和"踢掉某设备"。如果有,直接规划状态层,别在"纯无状态"的幻觉里浪费时间。Redis、数据库、或者专门的令牌管理服务,选一个你能运维的。
JWT仍然是个好工具,但它的卖点需要重新理解——不是"服务器不用存任何东西",而是"验证时不用存任何东西"。这两个词的区别,值一个周末的重构。
你们团队的退出登录是怎么实现的?黑名单查Redis还是数据库,还是干脆不管了等令牌过期?
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.