身份验证与授权
用户发出的每一个请求都会带出两个问题,而且这两个问题是按顺序回答的。
这是谁? 以及 他们能做什么?
第一个问题是身份验证(authentication),第二个是授权(authorization)。两者都常被简称为"auth",开头六个字母也一模一样,但它们解决的是完全不同的问题。
证明你是谁
身份验证只回答一个问题:眼前这个人真的是他自称的那个人吗?
作为用户,你已经接触过常见的验证方式:
- 用户名加密码。
- 用 Google 或 GitHub 账号登录。
- 在请求中附带 API key 或 token。
每一种方式都是在出示某种"证据",服务器的工作就是判断这份证据是否站得住脚。
一旦这一步出错,别人就能冒充成另一个人。这就是仿冒(spoofing),也就是 STRIDE 里的 S,也是那种一旦发生、其他所有防护都会失去意义的错误——因为应用现在是把正确的规则用在了错误的人身上。
这道门并不关心你想做什么,它唯一的工作就是判断你是不是你自称的那个人。
决定你能做什么
授权发生在身份验证之后,而且只有在身份已经确定的前提下才有意义:
- 用户读取自己的数据。
- 用户更新自己的资料。
- 老师查看自己学生的记录。
- 同样的请求换成学生发出,被拒绝。
一旦这一步出错,人们就会做出不该做的事。在 STRIDE 里这叫权限提升;在 OWASP Top 10 里这叫访问控制失效,而这一项已经连续多年排在榜首。
登录状态本身不是一项单一的权限,它只是一个起点,每次请求都要重新问一遍那个独立的问题。
它们运行的先后顺序
每一个到达受保护资源的请求,都会依次经过这两道关卡:
request ──▶ authentication ──▶ authorization ──▶ resource
(who are you?) (may you do this?)两个例子能把这个流程说清楚。用户读取自己的资料:请求到达,身份验证确认了他是谁,授权确认这份资料确实属于他,访问被允许。
管理员访问一个仅限管理员的接口:请求到达,身份验证确认了他是谁,授权检查他是否有管理员权限,访问被允许。
这两个例子的四个步骤里,只有一步不同。身份检查完全一样,权限检查才是两个请求分道扬镳的地方。
身份验证很强但授权很弱是不安全的。授权很严格但身份验证不可靠则毫无意义。
这就是为什么身份验证每次都排在最前面,也是为什么这一步出错会让后面所有环节都失去意义。
动手试一试
下面是四个场景。对每一个场景,判断这个失败属于身份验证问题还是授权问题,并说出它对应 STRIDE 里的哪个字母。
- 一个登录表单对用户名
admin接受任意密码。 - 一个已登录的顾客修改了 URL 里的 id,看到了另一个顾客的订单。
- 一个 API 接口接受完全不带 token 的请求。
- 一个客服人员可以删除账户,而这原本应该只有管理员才能做。
对照一下你的答案
| # | 失败类型 | 原因 | STRIDE |
|---|---|---|---|
| 1 | 身份验证 | 应用无法判断到底是谁在敲键盘,所以任何人都能冒充成 admin | 仿冒(Spoofing) |
| 2 | 授权 | 这个顾客的身份确实没错;问题出在没人检查过这份订单是不是属于他 | 权限提升 |
| 3 | 身份验证 | 请求根本没有出示任何声明,接口完全不知道调用者是谁。正确的响应是 401 | 仿冒(Spoofing) |
| 4 | 授权 | 这个客服人员的身份被正确识别了,出错的是权限检查。正确的拒绝方式是 403 | 权限提升 |
第 2 个场景有自己的专门名字:不安全的直接对象引用。用 OWASP 的说法,2 和 4 都属于访问控制失效。
第 2 和第 4 其实是同一类 bug 换了个样子出现,这也是访问控制失效之所以一直高居 OWASP 榜首的部分原因。在这两个案例里,应用其实都清楚地知道是谁在发起请求。
接下来会讲什么
身份验证只在登录时发生一次。授权则在之后的每一次请求中都会发生,而且每次都需要知道当前用户是谁。
这就留下了一个尴尬的空缺:HTTP 本身不会记住任何一次请求和下一次请求之间的关联。
应用是如何记住你的 讲的正是这个空缺带来的问题,也是本节接下来会讲到的三类解决方案。

