Skip to content
This page has been auto-translated and may contain errors.View in English

如何选择身份认证模型

三种模型,之前我们都是分开来看的。把它们放在一起,各自在优化什么就清楚了——因为每一种都是拿一部分东西去换另一部分。

每种模型到底在换什么

  • 有状态会话(Stateful sessions) 把一切都锚定在服务器上。身份信息在那里,控制权也在那里,所以它可预测、可更新、能瞬间撤销。作为代价,你有了一个必须持续扩容、保持同步的中心节点,而且服务器越加越多,这件事就越难。
  • 不透明的持有者令牌(Opaque bearer tokens) 指向服务器。身份信息依然存放在服务器上,只不过变成了一份小小的"令牌到用户"的映射,而不是完整的会话存储。服务器因此轻了很多,但还没有完全解脱,因为查找操作依然存在。这是一种务实的折中方案。
  • JWT 把"无状态"这个想法贯彻到底,把整个身份信息都塞进令牌本身。不需要查找,不需要共享存储,不需要协调,不给服务器增加负担。这是一个服务器可以立刻信任的签名对象,但一旦你需要撤销权限,麻烦就真的来了。
  • 委托身份认证(Delegated identity) 把这个责任完全移出了你的系统。你不用再操心密码,但换来的是要和某个身份提供方打交道,这段关系必须处理得当,其复杂度由你来承担。

有状态是为了掌控,无状态是为了扩展,委托认证是为了把信任这件事甩出去。

Juno每种模型到底在换什么 这里面没有哪个是"最优选项"。每一种都擅长某件事,同时在别的地方付出代价。

说来也怪,这反而让人轻松了:问题不再是"哪个才是正确答案",而是"这个应用能承受在哪方面表现差一点"。

Juno每种模型到底在换什么 这四种其实排在一条线上,值得这样去看。会话把一切都留在服务器端;不透明令牌保留一份精简的映射;JWT 什么都不保留;委托认证把整个问题都交到别处去解决。

沿着这条线走,你是在用控制权一步步换取扩展性。搞清楚自己站在哪一步,往往比罗列一堆特性更能快速平息一场架构上的争论。

Juno每种模型到底在换什么 表格没法体现的是运维层面的比较。会话需要一个必须保持可用的存储,于是这个存储的可用性就变成了你登录系统的可用性。

JWT 不需要存储,但需要密钥管理、密钥轮换,以及一旦密钥泄露该怎么办的预案。委托认证两者都不需要,但你的正常运行时间在某种程度上要看提供方的脸色。

这些东西在功能对比表里都看不出来,却会在一次事故里全部暴露出来。不管你选哪种模型,都值得问一句:它在凌晨三点出故障会是什么样子,到时候谁会被叫起来处理。

并排对比

有状态不透明令牌JWT委托认证
身份信息存放在哪服务器上服务器上,但很轻量令牌里提供方那里
每次请求查会话查令牌校验签名取决于之后你签发了什么
服务器存储完整的会话存储一份小映射
立即撤销可以可以不行部分掌握在对方手里
横向扩展需要共享存储需要共享存储无压力无压力
你放弃的东西协调成本部分协调成本反悔的余地控制权
Juno并排对比 "立即撤销"这一行是最该先看的。它是那种影响最大、也是后期最难改的差异。

如果你需要立刻把某个人踢出去,答案就是"某处得能查得到"。其他一切都是由此推导出来的。

Juno并排对比 注意不透明令牌和会话在表格里经常是同一列——这才是真实情况:不透明令牌只是同一个思路的轻量版,而不是另一个门派。

真正的分叉点在于"服务器能不能查到"和"服务器查不到",而 JWT 是唯一站在分叉另一边的那一列。

Juno并排对比 这些都不是一成不变的,而且往一个方向迁移比往另一个方向容易得多。从会话迁到令牌基本上是增量式的:先并行签发令牌,逐步迁移客户端,再逐步退役会话。反过来,从令牌迁回会话,就意味着要搭建你当初特意避开的那套存储,还要给所有人重新做一遍身份验证。

所以当两个选择难分伯仲时,选那个还留有"可查询"能力的模型,出错的代价更小。你随时可以以后再去掉状态;但把状态加回来,是一次没人愿意排上日程的迁移。

各自适合什么场景

  • 有状态 在服务器必须掌控全局时最合适:瞬间登出、角色权限实时生效、对用户状态的严格把控。它稳定、可预测,适合仪表盘、后台管理工具,以及任何控制权比横向扩展更重要的场景。
  • 无状态 在架构需要伸展空间时最合适。对于 API,或者任何需要在没有协调式会话状态的情况下扩展的场景,让客户端自带令牌能立刻消除这种摩擦。不透明令牌保持简单;JWT 则彻底省去了查找这一步。
  • 委托认证 在你意识到自己根本不想维护一套密码系统的那一刻就说得通了。或者当注册速度很重要,或者你想要一个比自己搭建更可靠的安全基线时也是如此。你的应用不再是"证明身份",而是变成"使用别人证明好的身份"。

最后值得一提的转变是:很多时候你并不需要只选一种。真实系统往往是组合使用——用委托身份认证来确认这个人是谁,用会话支撑主站点,再用 JWT 处理 API 调用。每一种都解决问题的不同侧面。

Juno各自适合什么场景 把它们组合起来听上去像是要多做很多工作,实际上通常反而更省事。每种模型只负责它擅长的那部分。

Google 负责证明你是谁,会话负责让你在网站上保持登录状态,令牌负责让你通过 API 的验证。三份活儿,三个工具。

Juno各自适合什么场景 组合使用时,要写清楚身份是在哪里被确立的、又是在哪里被使用的,因为这张图正是大家各说各话、争论不休的根源。

常见的结构是:由提供方完成身份认证,你自己的后端再签发自己的会话或令牌,下游的一切都信任你签发的东西,而不是提供方的。如果一直把提供方的令牌传来传去,集成就会变得一团乱麻。

Juno各自适合什么场景 组合使用有一点要留神:你加入的每一种模型都是一个入口,它们之间的安全强度必须一致。一个账号如果既能用密码登录,也能用委托登录,那它就有两种认证强度,攻击者会挑弱的那个下手。

密码登录这条路强制开启多因素认证(MFA),而委托登录那条路绕过了它,这种情况在实际产品中并不少见。同样常见的还有:一个只通过 Google 登录过的账号,密码重置流程却直接给它发一个会话。

这里始终成立的原则是:进入账号的每一条路径,都应该和保护它的最强手段一样强,否则那个"最强手段"就只是个摆设。

动手试一试

五个系统。给每个系统挑一种模型,并说出决定这个选择的关键属性。

  1. 一个给内部员工用的经典 Web 后台。管理员需要能瞬间登出,权限变更要立刻生效,安全性比扩展性更重要。
  2. 一个给移动应用提供服务的公开 API。
  3. 一个大型微服务架构,其中十几个服务都需要知道调用方是谁。
  4. 一个消费者应用,目标就是让注册尽可能快,最好能用 Google 或 Apple 登录。
  5. 一个跑在单台服务器上的个人小项目。
对照一下你的答案
#模型决定性的属性
1有状态服务器要掌控全局。改一个角色或者把某人登出,下一次请求就要立刻生效。对于普通的浏览器应用,会话简单又可靠
2无状态,两种都行每个请求自带一个令牌,服务器校验完就放行,不需要随着用量增长而维护一堆越来越多的会话
3无状态,具体是 JWT自包含,所以每个服务都能自行验证,而不用每个服务在每次调用时都去问一个中心服务器
4委托认证完全不用处理密码,注册速度也最快
5有状态能用就行,越简单越好。一台服务器,一张小小的会话表,一个 cookie。不需要令牌,不需要 OAuth,什么都不用运维

第 1 题和第 5 题从相反的方向得到了同一个答案,这才是有意思的地方。第 1 题选会话是因为控制权最重要;第 5 题选会话是因为别的都用不上。两者都跟"扩展性"无关——而大家往往误以为,扩展性才是决定这个选择的关键因素。

接下来去哪儿

四个问题现在会一直伴随你,遇到任何登录系统都能拿出来问:身份信息到底存在哪?服务器怎么知道这个人就是他自称的那个人?如果这份凭证泄露、过期或者被篡改了会怎样?这个应用适合哪种模型,为什么?

这是一套推理方式,而不是一份检查清单,正是它让一个陌生的身份验证系统也能被你读懂。

下一节话题完全变了。限流基础 不再关心"这个人是谁",而是开始关心"允许他们多久请求一次"。