跳转至

前言

我叫 Justin Richer。严格来说,我并不是那种科班出身的安全技术宅,尽管作为顾问,这是我在日常工作里“假装”自己是的角色。我的背景更多在协作技术:如何借助计算机让人们更高效地协同完成事情。即便如此,我接触 OAuth 已经很多年了——当年为了把我参与研究的协作系统连接起来,我实现过好几个早期的 OAuth 1.0 服务端和客户端。也是从那时候起,我逐渐意识到:如果你的应用架构想在真实世界里经得起折腾,就必须拥有一套足够可靠、可落地、好用的安全体系。差不多同一时期,我参加了早期的 Internet Identity Workshop 会议,人们在那里讨论“下一代 OAuth”:一种能够吸取 OAuth 1.0 在真实世界落地经验教训的新方案。后来,当 OAuth 2.0 在互联网工程任务组(IETF)启动推进时,我加入了工作组,一头扎进各种争论里。几年之后,我们最终完成了一份规范。它并不完美,但确实很好用,大家也能理解,于是迅速流行开来。

我一直参与 OAuth 工作组的工作,还曾担任 OAuth 的动态注册(RFC 7591 和 7592)与令牌自省(RFC 7662)扩展的编辑。如今,我是 OAuth 持有证明(PoP)系列规范部分内容的编辑或作者,同时也是多个 OAuth 及其相关协议的配置文件(profile)与扩展的技术编辑。我参与了 OpenID Connect 核心规范的编写;我和团队还实现了一套口碑相当不错的 OAuth 与 OpenID Connect 服务端/客户端套件——MITREid Connect。随后我发现自己需要面向各种不同的受众讲解 OAuth 2.0,并在形形色色的系统上实现它。我教过课程、做过演讲,也写过一些相关文章。

因此,当 Antonio Sanso——一位本身就备受尊敬的安全研究员——来找我合写这本书时,我觉得再合适不过了。我们先梳理了一下市面上关于 OAuth 2.0 的书,结果并不满意。我们看到的大多数资料都围绕某个特定服务:比如“如何写一个 OAuth 客户端去对接 Facebook 或 Google”,或者“如何让你的原生应用访问 GitHub 的 API”。如果你只关心这些,现成材料确实很多。但我们没有看到一本能带读者完整走过 OAuth 体系的书:解释它为何这样设计,指出它的缺陷与边界,同时也讲清楚它的优势。我们认为需要一种更全面的写法,并决定把它做到最好。也正因如此,本书不会针对任何具体的真实世界 OAuth 提供方,也不会深入某个特定 API 或垂直领域。相反,本书会把 OAuth 当作一个完整系统来讲,让你在“转动曲柄”时看清所有齿轮如何咬合运转。

我们搭建了一套代码框架,希望读者能把注意力放在 OAuth 的核心要点上,而不是陷入某个平台实现细节的泥潭。毕竟,我们不想写成“如何在某个当红平台上实现 OAuth 2.0”,而是“OAuth 2.0 的底层机制究竟如何运作,从而你可以在任何平台上正确使用它”。因此我们选择了相对简单的 Node.js 框架,基于 Express.js,并大量使用库代码来尽可能屏蔽平台相关的怪癖。当然,这终究是 JavaScript,一些“怪味道”还是会不时冒出来——任何平台都会如此。但我们希望,你能够把这里的方法与思路迁移到你选用的语言、平台与架构中。

既然提到历史:我们究竟是怎么走到今天的?故事要从 2006 年说起。当时包括 Twitter、Ma.Gnolia 在内的几家 Web 服务公司拥有互补的应用,并希望用户能把它们连接起来。那时候,建立这类连接通常的做法是:让用户提供远端系统的凭据,然后把这些凭据发送给 API 使用。然而,相关网站使用了一种分布式身份技术 OpenID 来完成登录,因此根本没有可用于 API 的用户名或密码。

为了解决这个问题,开发者们试图创建一种协议,让用户能够把对 API 的访问权限委托出去。他们的新协议借鉴了多个同类概念的私有实现,包括 Google 的 AuthSub 和 Yahoo! 的 BBAuth。在这些方案中,客户端应用会先获得用户授权,然后拿到一个令牌,之后就能用该令牌访问远端 API。这些令牌都会发放一个公开部分和一个私密部分;协议还使用了一种新颖(但回头看其实相当脆弱)的密码学签名机制,使其可以在非 TLS 的 HTTP 连接上使用。他们把这个协议命名为 OAuth 1.0,并以开放标准的形式发布在网上。它很快被广泛采用,多种语言的免费实现也随着规范一起出现。由于效果很好、开发者也很喜欢,就连那些最初启发了 OAuth 的大型互联网公司也很快弃用了自家的私有机制。

和许多新的安全协议一样,OAuth 1.0 在早期就被发现存在漏洞,于是诞生了 OAuth 1.0a,用以修补一个会话固定(session fixation)漏洞。这个版本后来在 IETF 中被正式整理为 RFC 5849。此时,围绕 OAuth 协议的社区开始形成,新的使用场景不断出现并被实现。其中一些把 OAuth 推到了它原本并不打算涉足的领域,但这些“超适应症”用法仍然比当时所有可选方案都更好用。尽管如此,OAuth 1.0 依旧是一个“大而全”的单体协议,试图用一种机制覆盖所有场景,而它正逐渐进入并不舒适的地带。

RFC 5849 发布后不久,Web Resource Access Protocol(WRAP)随之出现。这个提议协议继承了 OAuth 1.0a 的核心要素——客户端、委托与令牌——并把它们扩展到更多不同的使用方式。WRAP 去掉了 OAuth 1.0 中许多更令人困惑、也更容易出问题的部分,例如自定义的签名计算机制。经过社区长时间的讨论,最终决定以 WRAP 作为新一代 OAuth 2.0 协议的基础。OAuth 1.0 是单体的,而 OAuth 2.0 则是模块化的。OAuth 2.0 的模块化使它成为一个框架,能够以 OAuth 1.0 在实践中使用过的各种方式来部署与应用,却无需扭曲协议的核心要素。可以说,OAuth 2.0 提供的是一套“配方”。

2012 年,OAuth 2.0 的核心规范在 IETF 获得批准,但社区并没有就此停下。模块化的设计进一步通过拆分规范得到强化:RFC 6749 说明如何获取令牌,而 RFC 6750 说明如何在受保护资源上使用某一种特定类型的令牌(Bearer 令牌)。此外,RFC 6749 的核心部分还定义了多种获取令牌的方式,并提供扩展机制。OAuth 2.0 没有试图定义一种复杂到能覆盖所有部署模型的方法,而是定义了四种不同的授权类型(grant type),分别适配不同的应用类型。

今天,OAuth 2.0 已经成为 Web 上最主流的授权协议。大到互联网巨头,小到创业公司,乃至各类企业以及几乎所有介于其间与其外的组织,都在使用它。围绕 OAuth 2.0,一个由扩展、配置文件乃至完全构建在其之上的整套协议组成的生态系统也随之繁荣起来,人们不断发现这种基础技术的更多新颖用法。我们希望本书不仅能帮助你理解 OAuth 2.0 是什么、它为什么会以这种方式工作,也能帮助你把它用好,用来解决你自己的问题并构建你自己的系统。

JUSTIN RICHER