速评:8 月 31 日发在 dev.to 的那篇 sessions vs JWT,正文写得挺稳,最不设防的那句话被评论区当场拆掉。
作者 Athreya,标签挂了 webdev、security、backend、programming 四个,9 月 6 日又编辑过一轮。标准的"老话题新写法",我第一遍扫到就是这么归档的,然后划走了。一个多月后翻回来,是为了底下那几条评论。
正文做对的事先说。它把"有状态 vs 无状态"这个二分法扔了,换成两个能算账的东西:状态多久买一次单,以及你愿意让错的东西活多久。落到数字上就是一行对照——sessions 每请求付费、从不出错;裸 JWT 从不付费、可能几个小时都在服务错误的人;refresh 折中,偶尔付费、窗口 15 分钟。
作者最狠的一句在这儿:refresh token 本身就是一个指向服务端状态、随时可以删掉的不透明 id。那玩意儿就是 session,区别只是检查频率从每请求变成了每 15 分钟一次。号称已经把 session 扫地出门的团队,多数只是换了个东西每 15 分钟回一次头。
然后是转折。
"sessions 每次请求都付费,且从不出错"——前半句对,后半句是理想模型。
Vinh Nguyen 那条最干净:把一个用户从 admin 降成 member,但不动 session 那行记录,下一个请求它照样自报 admin,因为它读的是那行记录,而那行记录没人去改。更别说 session 行往往连 exp 都没有。他还给了个当场能验的办法——去数据库改角色、不碰 session 行、发一个请求过去,如果还过,说明你系统里有两个独立走时的钟。
这句的分量比看起来大。身份是一个钟,权限是另一个钟,吊销是第三个。说 sessions"从不出错",无非是你在盯着第一个钟看。全网一边倒吹 JWT 无状态的时候没人问过"几个钟",反过来这篇给 session 发的免检金牌,逻辑上是同一回事——只是这回护的是另一边的短。
Luca 在下面补了另一半。只要 session 行保持精简——随机 id、用户 id、过期时间——每请求查一次的成本低到可以不讨论,单后端这套场景里,JWT 的可扩展性论据基本就消失了。真正的痛点出现在 session 行被当成杂物间堆了好几年之后,那属于表设计事故,不是架构缺陷。他还有一条更狠的:一旦上了 token 轮换加重放检测,存 token family、重放一个就吊销整条血缘链,那个 refresh 端点实质上就是一次会话检查。绕了一圈,检查没省,代码多了。
Niuniu Ox 的现场记录也值得抄下来。他在 Cloudflare Workers 加 Neon 的免费层上跑 JWT,refresh 轮换把他直接逼回了服务端状态,"无状态"被他改口成"除了吊销列表之外无状态",最后发现 Postgres 里一张小 session 表的代码量反而更少。他给出的决策转折点是 logout-anywhere 语义——也就是"我现在就要让这个人下线"的能力。最省