应用是如何记住你的
你登录了。你点了个东西。服务器完全不知道你是谁。
HTTP 是无状态的:每个请求都是全新到达的,既不记得上一个请求,也没有内建的方式把两者关联起来。刚刚成功的那次登录,什么痕迹都没留下。
所以,一个需要在多次请求之间认出你的应用,必须把身份信息存放在某处,并在之后的每一次请求里把它取回来。
这一节要回答的其实只有一个问题:身份信息存在哪里?
几乎所有 Web 登录流程都用以下三种方式之一来回答这个问题。
服务器来记住你
在有状态身份认证(stateful identity)中,信息由服务器保管。
你登录时,服务器会创建一个会话(session),也就是一小份代表你的数据:用户 id、名字,也许还有角色。这个会话保存在服务器上。浏览器拿到的是一个携带会话 id 的 cookie,仅此而已,之后每次向该域名发起请求时都会自动带上它。服务器拿到这个 id,查出对应的会话,就又知道你是谁了。
这是传统 Web 应用的模式,也是很多框架的默认做法。如果你用过 express-session,它做的就是这件事。
关于你的一切重要信息都留在服务器上,而那个 id 只有能连到那台服务器的人才用得上。
客户端自己带着凭证
在无状态身份认证(stateless identity)中,服务器不存储任何关于你的信息。
客户端持有一个令牌(token),并在每次请求中带上它。服务器检查这个令牌就能作出响应,不需要在请求之间去查找你是谁。
这种方式很适合 API、移动应用和分布式系统,因为在这些场景里,服务器间共享内存要么很别扭,要么根本不可行。如果你调用过某个 API 并收到过 401 Unauthorized: missing authorization header,那就是这种模式在告诉你:把凭证带上。
它有两个版本,两者的差异大到值得各自开一章来讲。不透明的持有者令牌(opaque bearer token)是一串没有意义的随机字符串,由服务器去查找对应信息。JSON Web Token 则把身份信息直接签名后带在令牌本身里,完全不需要查找。
它照样有一个装满用户信息的数据库。它不再保留的,是"你现在处于登录状态"这条记录。
由别人来为你担保
在委托身份认证(delegated identity)中,一个你信任的第三方代替你确认用户的身份。
你的应用去问 Google 是否认识这个人。Google 完成登录流程,然后把一个证明结果的令牌交给你的应用。每一个"使用 Google 登录""使用 GitHub 登录""使用 Apple 登录"的按钮,做的都是这件事。
它的吸引力在于,你不用再自己维护一套密码系统。不用存密码,不用做重置流程,也不用担心泄露你从未持有过的凭证。OAuth 与委托身份认证 会讲清楚这个交接过程到底是怎么运作的。
这就是为什么你会被跳转到 Google 的页面,然后又跳回来。证明这一步是在那边完成的。
并排比较
| 有状态 | 无状态 | 委托 | |
|---|---|---|---|
| 身份信息存在哪里 | 服务器上 | 客户端持有的令牌里 | 由服务提供方保管 |
| 每次请求的成本 | 一次查找 | 一次签名校验,或一次查找 | 取决于你最终用的是哪种令牌 |
| 撤销访问权限 | 即时生效 | 比较困难,通常要等过期 | 部分取决于服务提供方 |
| 能否横向扩展 | 需要共享存储 | 可以自由扩展 | 可以自由扩展 |
| 主要牺牲了什么 | 服务器间的协调成本 | 反悔的能力 | 控制权和独立性 |
它们之中没有哪个是"正确答案"。它们各自针对不同的目标做了优化,这也是为什么真实系统往往会同时用上不止一种:用委托登录来确认你是谁,用会话来支撑网站,用令牌来支撑 API。
值得带到下一章的一句话是:有状态是为了控制,无状态是为了扩展,委托是把这个难题交给别人去解决。
动手试试
针对下面每个系统,说出你预期它会用哪种模式,以及决定这个选择的那个关键属性。
- 一个内部管理后台,撤销某人的访问权限必须立刻生效。
- 一个为拥有几十万用户的移动应用提供服务的公共 API。
- 一个照片应用,整个注册流程就是一个"使用 Google 继续"按钮。
- 一个微服务架构,其中十几个服务都需要知道调用者是谁。
对照一下你的答案
| # | 模式 | 决定性的属性 |
|---|---|---|
| 1 | 有状态 | 即时撤销。删掉会话,下一个请求就是匿名的。没有其他方案能做到这一点。 |
| 2 | 无状态 | 随着流量增长,不需要协调一个共享的会话存储,而且移动客户端持有令牌也很方便。 |
| 3 | 委托 | 不用搭建、存储或担心泄露密码系统,注册速度也是最快的。 |
| 4 | 无状态,具体来说是 JWT | 每个服务都能自己验证令牌,不需要在每次调用时都去问一个中心服务器。 |
第 1 题是最有意思的一个。它是列表里规模最小、扩展压力最小的系统,却用了扩展性最差的那种模式,因为在这里,控制力比增长更重要。
接下来去哪里
三种方式,各自用不同的思路解决同一个问题。
会话与 Cookie 会把第一种方式拆开来细看:服务器存了什么,浏览器带着什么,以及哪些失误会把一个原本扎实的模式变成一个漏洞百出的模式。

