16.总结与结语¶
恭喜你,终于把这本书从头到尾读完了!希望你的阅读体验能像我们的写作过程一样愉快。在这段旅程中,我们涵盖了相当多的内容——OAuth 并不是一个简单的协议,它由许多可变的组成部分构成,而且这些部分有多种不同的组合方式。起初这可能会让人望而生畏,但我们希望,当你掌握了整个机制的运作原理之后,你会发现它已经尽可能简单了——但也不会更简单。希望你在我们带你一起搭建 OAuth 各个组件的过程中所学到的知识,能为你之后的探索之旅提供指引。在最后一章里,我们会用几页篇幅聊一聊更宏观的全局视角。
选对工具¶
OAuth 是一个强大的委托协议,但我们知道,你并不是为了用 OAuth 而用 OAuth。OAuth 里的 “auth” 指的是 “authorization(授权)”,而除非是要授权某种操作,否则没人会去用一套授权协议。我们也知道,你最初之所以不得不用 OAuth,多半是为了完成别的事:一些有用的、漂亮的、甚至令人惊艳的功能。OAuth 只是你手中众多工具之一,用来解决某一类特定问题——无论是保护 API、构建访问 API 的客户端,还是搭建把整个系统串起来的整体安全架构。和所有工具一样,理解它们如何工作、擅长什么、适用什么场景,既重要也很有价值。毕竟,用锤子也不是不能把螺丝钉敲进墙里,但用螺丝刀会顺手得多。我们希望你能知道什么时候 OAuth 适合你的问题,同样重要的是,什么时候它并不适合。
我们希望这本书能带给你足够深入的知识,让你知道该在工具箱里什么时候拿出 OAuth,以及该如何把它应用到你的具体问题中。我们并不想把这本书写成某些头部互联网厂商的 OAuth 实现指南,更不想只教你如何使用某个特定的 OAuth 库。我们相信,针对这类聚焦问题的资料,网上已经有大量文档可查。相反,我们希望你在读完本书之后,对 OAuth 足够熟悉——无论你用的是哪个平台、哪个应用场景,都能自如地使用它。
我们知道,几乎没有读者会从零开始端到端实现一整套 OAuth 生态。那么,我们为什么还要如此大费周章?本书的内容与练习,应该能让你对 OAuth 协议及其所有相关组件形成深刻的理解与认知,能够看清数据在系统中是如何流动的。通过从头搭建完整生态,我们希望当你的客户端发出请求却收到一个奇怪的响应时,你能更好地判断到底发生了什么;也希望当客户端以你没完全预料到的方式把东西发到资源服务器时,你能理解其中缘由。通过从“另一侧”做实现练习,我们也希望你能更包容、更理解系统是如何被构建出来的——但又不会包容到不小心放开一个安全漏洞。毕竟,到现在你应该已经很清楚,OAuth 流程里的每个环节之所以存在,都是有充分理由的。
如果你没有从头到尾读完整本书,而是只跳到与你当前问题最相关的部分,我们不会评判你。事实上,我们自己读这种大型技术书时也经常这么做。不过,现在正是回过头再看看系统其他部分的好时机。你在做客户端?那就试着把授权服务器也实现一遍。你在做浏览器内应用?不妨看看原生应用那一节。你在做像 OpenID Connect 这样的认证协议?回头再深入看看 OAuth 协议本身的核心机制,体会一下在 OIDC 中,认证信息是如何通过网络被携带和传递的。
关键决策的制定¶
正如我们在全书中反复强调的那样,OAuth 是一个“选项很多”的框架。要在这些选项之间做出取舍并不容易,但我们希望你到现在已经对该怎么选有了清晰的判断。我们也希望你能看清 OAuth 框架中每一种选项各自的价值,以及它们为什么会被纳入进来。与其设计一个“看似统一但对任何主要场景都不太贴合”的单一协议,不如像 OAuth 2.0 这样,提供一组可组合的构件,让我们能够在不同场景下做不同的事。我们希望本书能帮你掌握:哪些构件该用、什么时候用。
我们也明白,在这个过程中很容易想走捷径:用看起来更简单的方案,替代真正最适合你用例的选择。比如,隐式流程很简单,为什么不干脆什么场景都用它,省掉授权码那一套麻烦?又或者,既然在 API 上加个参数就能让客户端告诉我们“它代表哪个用户在操作”,那为什么还要去打扰用户?然而,正如本书漏洞章节所展示的那样,这类“省事”的做法最终往往会带来痛苦和妥协。安全这件事再诱人也没有捷径可走;想要真正的安全,就不存在所谓的“好捷径”。
最重要的决策之一,是先判断到底要不要使用 OAuth 2.0。OAuth 之所以成为保护 API 的首选协议,正是因为它灵活、实现门槛相对低;但也正因为如此,它也很容易被误用——把它当成解决各种问题的“万能钥匙”。好消息是,OAuth 的结构与理念具有可迁移性:最近也已经有人尝试将这套流程部署到非 HTTP 协议上。与此相伴的另一个问题是:OAuth 对你的问题是否“足够”。正如我们看到的,在真实世界里,OAuth 经常会与其他技术组合使用,用来解决远不止“权限委托”这么简单的更大问题。我们认为这是一件好事,它促成了一个丰富且繁荣的生态。
一旦你决定要构建一套 OAuth 系统,首先应该问自己的问题是:该使用哪种授权类型(authorization grant type)?我们在「OAuth 之舞」深入讲解、并在第 3~5 章逐步搭建的授权码(authorization code)授权类型,应当是大多数场景的默认选择。只有当你的系统恰好满足少数几类特定的优化场景时,才需要考虑选择其他授权类型。而这些“优化空间”在很大程度上取决于你要构建(或预期会被构建出来)来访问 API 的客户端类型。比如,如果客户端完全运行在浏览器中,那么隐式流程是合理的;但如果你构建的是原生应用(native application),授权码流程仍然要好得多。如果你的调用并不是代表某个特定用户执行,那么客户端凭证(client credentials)流程会非常适合;但如果你确实是在代表某个具体用户执行操作,那么更好的方式是让该用户参与流程,使用一种交互式授权类型。这一点即便在企业部署中用户并不实际做出授权决策时也同样成立,因为“已认证用户的存在”可以作为整体安全架构中的一根重要支柱。
当然,即使你把所有核心决策都做对了,部署或使用过程中仍然可能出问题。这就是安全的本质:它从来不是“顺手就有”的。OAuth 通过把复杂性从客户端侧转移到授权服务器侧,确实在很大程度上帮助大家把事情做对;但即便如此,仍然必须以正确的方式使用这些组件。这也包括确保所有周边与底层系统(例如 TLS)按预期正常运行。OAuth 在这一点上同样能提供帮助:它的安全模型建立在多年来、跨各种系统的部署经验之上。只要遵循一些最佳实践,并在合适的场景正确使用 OAuth 的各个部分,即便是一个规模很小、一次性的 API 提供方,也能获得与当今互联网上最大型 API 相同等级的安全性与委托能力。
更广阔的生态系统¶
正如本书贯穿始终所展示的那样,OAuth 的设计初衷是完成一项工作——安全委派——并把这件事做好。OAuth 对某些事情并不擅长,甚至完全不处理;但正如我们在本书后半部分看到的,这并不是坏事。随着时间推移,OAuth 提供了坚实的基础,一个庞大而丰富的生态系统得以在其之上建立起来。就像 OAuth 框架本身提供了一系列选项来解决不同问题一样,围绕它的生态同样提供了许多可与 OAuth 搭配使用的选择,用以满足形形色色的需求。
OAuth 工作组有意将真实安全架构中若干关键要素排除在规范之外,比如访问令牌本身的格式。毕竟,既然无论如何都要生成令牌,为什么不干脆规定所有人该如何制作令牌呢?这种有意识的“留白”,反而促成了 JOSE、JWT 等互补技术的发展——它们与 OAuth 配合良好,但并不依赖 OAuth。JOSE 将 JSON 的简洁性与高级密码学能力结合在一起,同时仍然易于开发者正确实现。另一方面,在需要令牌足够紧凑,或根本不要求令牌自包含的场景下,令牌自省(token introspection)提供了可行的替代方案。更重要的是,得益于 OAuth “只做一件事,并把它做好”的理念,这两种方案可以相互替换,甚至组合使用,而客户端往往完全无感。
还有一些扩展是为应对特定场景而加入 OAuth 的。例如,「常见的 OAuth 令牌漏洞」介绍的 PKCE 扩展,用于帮助防止在原生移动应用中通过本地、应用专用 URI scheme 使用授权码时发生授权码被窃取。PKCE 在这一狭窄场景之外意义不大,但在该场景内效果极佳。同样,令牌吊销(token revocation)扩展为客户端提供了一种主动丢弃令牌的方式。为什么这不是 OAuth 的通用能力?在某些系统中,OAuth 令牌完全自包含且无状态,并不存在一种合理的方式将“吊销”事件传播到由资源服务器组成的分布式系统中;而在另一些系统里,客户端被假定对令牌状态完全无感,这类主动措施也就意义不大。不过,对于那些令牌有状态、且客户端比“完全无脑”稍微聪明一点的生态来说,吊销机制能在既有安全之上再叠加一层额外防护。
类似地,OAuth 还可以用来构建许多不同的协议。我们在「使用 OAuth 2.0 进行用户认证」中详细看到,尽管 OAuth 本身不是认证协议,但可以用它来构建认证协议。进一步地,我们也看到,像 HEART、iGov 这类 OAuth 的配置文件(profile)与应用实践,能够将庞大社区聚合起来实现互操作性。这些工作通过锁定 OAuth 的一组选择与扩展,提供可供他人遵循的模式与配方。OAuth 也可以用来构建更复杂的协议,例如 UMA:它在 OAuth 的委派模式基础上扩展到多方参与的场景。
社区¶
OAuth 在互联网上拥有一个充满活力的社区,资源丰富、讨论热烈,还有许多专家随时愿意伸出援手。正如你在本书中看到的那样,构建一个 OAuth 生态系统需要做出大量决策,而很可能在类似场景下,互联网上已经有人替你走过一遍并做出了同样的选择。你会找到开源项目、大型公司、敏捷的初创团队、一波又一波的顾问,以及(我们希望)至少一本还不错的书,帮助你真正弄清楚 OAuth 到底是怎么回事。选择这套安全体系的并不只有你一个人;而在安全领域,“众人之眼、众人之手”共同审视与打磨,往往意味着更强的保障。
OAuth 工作组仍在持续推进协议及其扩展的演进,而你(没错,就是你)也可以加入讨论。1 你觉得自己能做得比 OAuth 更好?你的使用场景是否需要在 OAuth 之上加一点“独门配方”?你是否需要一种在假设条件和部署特性上都不同的授权类型(grant type)?那就尽管把这些问题带到工作组里来讨论。
由于 OAuth 并不是由某家公司、甚至不像许多开源项目那样由某个联盟来定义和主导,因此它没有官方的品牌形象和市场推广来提升辨识度。于是,社区再次站了出来。OAuth 的“公交代币”标志由 Chris Messina 绘制并提交给社区,尽管严格来说并非官方标识,却迅速走红。OAuth 协议甚至还有一个”非官方但很酷”的吉祥物:OAuth-tan(见图 16.1)2
这个社区之所以能够自然发展壮大,离不开几个关键因素。首先也是最重要的一点,OAuth 解决了人们确实存在的现实问题。OAuth 1.0 和 2.0 推出时,API 经济与移动应用生态才刚刚起步,而 OAuth 及时补上了一层迫切需要的安全机制。其次,同样重要的是,OAuth 是一个开放协议。它不归任何一家公司所有或控制,任何人都可以在无需支付版税或许可费用的前提下实现它。这也意味着,围绕 OAuth 已经形成了一个庞大的生态:大量软件、类库和演示项目在各种形态下都能开箱即用地支持 OAuth。那如果你钟爱的平台上没有现成实现怎么办?很可能已经有其他语言的类似实现,你完全可以借鉴并快速移植,直接拿来用。
OAuth 相对来说很简单。没错,你已经学习了三百多页的相关材料,而且如果不够谨慎,确实有不少地方可能很快踩坑。但与此前许多系统的深度复杂性相比(例如 Kerberos、SAML 或 WS-*),它几乎可以说是“简单到不值一提”。尤其是客户端——正如我们所见,它们构成了生态系统的大多数——实现起来更是如此。这种“足够简单”的平衡,让 OAuth 得以在过去往往回避安全协议的方式与场景中被采用,从而吸引了更多开发者加入这一阵营。
未来¶
我们在「OAuth 之舞」看到的 OAuth “舞步”(OAuth Dance)如今是互联网上最流行的流程之一,但今天的流行并不意味着未来也一定如此。尽管我们认为 OAuth 2.0 的能力与相对简洁性决定了它仍会在相当长一段时间内存在,但技术终究会不断向前演进。
就在 OAuth 的世界里,我们已经通过 PoP(Proof-of-Possession,持有证明)令牌标准化的工作看到了这种演进趋势。PoP 规范将 JOSE 技术以一种新的方式应用到 OAuth 中,构造出 PoP 令牌,相比 bearer token(不记名令牌)能提供更高等级的安全性,但代价是引入了一定复杂度。因此,它们很可能会按需部署,并且常常与 bearer token 并行使用。未来也可能出现其他形态的令牌,例如与 TLS 层绑定的令牌,或采用一些新颖的密码学机制。
随着越来越多扩展与组件被加入 OAuth,我们很可能会看到 OAuth 2.1 或 OAuth 3.0,将这些内容整合为一套单一、连贯、且比今天这种“自然生长”的文档集合更易理解的规范体系。不过,如果这种重构真的会发生,那也会是在相当遥远的未来。
最终,几乎不可避免地会有那么一天,OAuth 本身会被更新、更好的东西彻底取代。OAuth 是其时代的产物,直接依赖于 HTTP、Web 浏览器和 JSON。尽管这些技术的生命周期可能很长,但它们不可能永远像今天这样以同等的普遍性和形态存在。就像 OAuth 在许多安全架构中取代了 Kerberos、WS-Trust 和 SAML,并进入了那些旧协议无法胜任的新场景一样,未来也一定会出现某种新技术,最终接过 OAuth 的位置,成为值得关注的安全协议。
在那之前,OAuth 依然非常值得理解、值得投入建设,并且今后也会继续如此。
总结¶
这段旅程相当漫长。我们从 OAuth 2.0 的核心定义出发,沿着参与方、组件以及它们之间的连接一路深入,从零搭建起一个完整的生态系统。随后我们退一步,审视可能出错的地方以及如何修复。接着,我们更深入地考察了围绕 OAuth 的协议世界,包括 OpenID Connect 和 UMA。最后,我们把目光投向未来,讨论了 PoP 令牌和令牌绑定等方向,看看 OAuth 可能会走向何处。
接下来呢?现在,是时候开始构建你的系统了:挑选优秀的库;为开源项目做贡献;参与标准社区。毕竟,你构建 OAuth 能力并不是为了 OAuth 本身;你构建的是一套 OAuth 能力,用来保护并加固某个你自己以及整个世界都在意的功能。既然你已经掌握了用 OAuth 实现委托、授权以及诸多相关安全细节的方式,就可以把注意力放回真正的目标:构建你的应用、API 或生态系统。
感谢一路同行。希望你和我们一样享受这段旅程,也希望你同样喜欢我们为你所做的引导。
-
经由后藤由希授权使用。 ↩