OAuth协议的设计目的是:让最终用户通过OAuth将他们在受保护资源上的部分权限 委托 给客户端应用,使客户端应用代表他们执行操作。为实现这一点,OAuth在系统中引入了另外一个组件: 授权服务器 (如图1-7所示)。
图 1-7 OAuth授权服务器自动发送服务专用的密码
受保护资源依赖授权服务器向客户端颁发专用的安全凭据——OAuth访问令牌。为了获取令牌,客户端首先将资源拥有者引导至授权服务器,请求资源拥有者为其授权。授权服务器先对资源拥有者进行身份认证,然后一般会让资源拥有者选择是否对客户端授权。客户端可以请求授权功能或权限范围的子集,该子集可能会被资源拥有者进一步缩小。一旦授权请求被许可,客户端就可以向授权服务器请求访问令牌。按照资源拥有者的许可,客户端可以使用该令牌对受保护资源上的API进行访问(如图1-8所示)。
图 1-8 完整的OAuth工作过程
在这个过程中,没有将资源拥有者的凭据暴露给客户端:资源拥有者向授权服务器进行身份认证的过程中所用的信息是独立于客户端交互的。客户端没有功能强大的开发者密钥,无法随意访问任何资源,而是必须在得到有效的资源拥有者授权之后才能访问受保护资源。虽然大多数OAuth客户端可以向授权服务器进行身份认证,但仍然需要得到授权后才能访问资源。
用户通常不必查看或者直接处理访问令牌。OAuth不需要由用户生成令牌并粘贴到客户端,而是简化了这一过程:客户端请求令牌,用户对客户端授权。然后由客户端管理令牌,用户管理客户端应用。
以上是对OAuth工作原理的一般性概述,但实际上OAuth拥有多种获取访问令牌的方法。第2章将介绍OAuth 2.0的授权码许可类型,详细讨论其工作过程。其他获取令牌的方法将在第6章介绍。
上一节中列出的许多“传统”方法都是密码反模式的案例,它们通过共享机密信息(密码)来直接代表当事方(用户)。用户通过与应用共享密码,使应用能够访问受保护的API。然而,正如我们已经揭示的那样,这种做法在现实中存在很多问题。密码本身可能被盗或者被猜到;同一个用户可能在不同的服务上使用完全相同的密码;为了以后能继续访问API而保存密码会使得密码更易被盗。
HTTP API最开始是如何引入密码保护功能的呢?我们可以从HTTP协议的历史及其安全手段看出端倪。HTTP协议制定了一个机制,用户可以凭借该机制在浏览器中使用用户名和密码向一个网页进行身份认证,这就是所谓的HTTP基本认证协议(HTTP basic auth)。还有一种更安全的认证协议,叫作HTTP摘要认证(HTTP digest auth)。但是就我们的目的来说,它们没有什么区别,因为它们都假设用户在场,并且要求向HTTP服务器呈递用户的用户名和密码。此外,由于HTTP是一个无状态的协议,因此每一个HTTP事务都要呈递这些凭据。
鉴于HTTP原本是一个文档访问协议,这一切都是合理的。但是Web的规模和应用范围自那以后已显著扩大。作为一个协议,HTTP不会区分一个事务是由用户通过浏览器发起的,还是通过其他软件发起的。这种基本的灵活性是HTTP协议得到普及的关键原因。但结果是,除了面向用户的(网页)服务之外,当HTTP开始被用于直接访问API时,其现有的安全机制顺理成章地被沿用到新的应用场景中。这个不明智的技术决策导致了一种长期存在的错误做法:为API和网页服务不断地呈递密码。虽然浏览器可以使用cookie或者其他会话管理技术,但是访问Web API的HTTP客户端没有这样的机制可用。
OAuth从一开始就被设计成一个用于API的协议,其中主要的交互过程都是在浏览器之外进行的。OAuth的整个流程通常是由最终用户在浏览器中启动的,实际上这也正是委托模式的灵活性和优势所在。但是最终接收令牌、使用令牌访问受保护资源的步骤对用户是不可见的。实际上,OAuth的一些主要事务过程都发生在用户不在场的情况下,客户端仍然能够代表用户执行操作。OAuth让我们摒弃HTTP基本协议中的观念和假设,将一种功能强大、安全的方式引入现今的API体系。
委托概念是OAuth强大功能的根基。虽然OAuth经常被称作授权协议(这是RFC中给出的名称),但它也是一个委托协议。通常,被委托的是用户权限的子集,但是OAuth本身并不承载或者传递权限。相反,它提供了一种方法,让客户端可以请求用户将部分权限委托给自己。然后,用户可以批准这个委托请求。被批准之后,客户端就可以去执行那些操作了。
以照片打印为例,照片打印服务可以询问用户:“你是否在这个存储服务上存放了照片?如果是,我可以帮你将它们打印出来。”然后用户被引导至照片存储服务,存储服务也会询问:“打印服务想要获取你的照片,你同意吗?”用户可以决定是否同意,即决定是否将访问权限委托给打印服务。
在这里,委托协议和授权协议的区别是很重要的,因为OAuth令牌中携带的授权信息对系统中的大部分组件是不透明的。只有受保护资源需要了解授权信息,只要它能从令牌中得知授权信息(既可以直接从令牌中获取,也可以通过某种服务来获取),它就可以按要求提供API服务。
连接到网络世界
OAuth中的许多概念并不新颖,甚至是从先前的安全体系中借鉴而来的。然而,OAuth 2.0是一个为Web API世界而生的协议,访问这些API的是客户端软件。OAuth 2.0框架提供了一系列用于连接这些应用软件和API的工具,适用于各式各样的场景。在后面的章节中,你将会看到,同样的核心概念和协议可用于连接网页应用、Web服务、原生和移动应用,甚至物联网中的小型设备(使用扩展协议)。纵观这一切,OAuth依赖一个相互连接的网络世界,并使得在此基础上构建新生事物成为可能。
由于OAuth的委托过程需要资源拥有者的参与,因此它提供了一种在很多其他安全模型中不存在的可能性:重要的安全决策可以由最终用户来做。传统上,安全决策一直由集权机构负责。由集权机构决定谁可以使用服务、使用什么客户端以及以何种目的使用。OAuth则允许集权机构将某些决策权交到最终使用软件的用户手中。
OAuth系统常遵循TOFU原则:首次使用时信任(trust on first use)。在TOFU模型中,需要用户在第一次运行时进行安全决策,而且并不为安全决策预设任何先决条件或者配置,仅提示用户做出决策。这个过程可以简单到只是询问用户“要连接到新的应用吗”。当然,很多实现允许在这个步骤中进行更多控制。无论用户遇到的是哪种情况,只要具有相应的权限,他们就能做出安全决策。系统会记住用户的决策,以便以后使用。换句话说,只要首次建立了授权关系,系统就会在后续的处理过程中继续信任用户的决策:首次使用时信任。
TOFU是强制要求吗?
OAuth实现并不强制要求采用TOFU方法管理安全决策,但是这两种技术经常结合使用。这是为什么呢?因为要求用户在一个上下文环境中做出安全决策具有很强的灵活性,而不断地要求用户做决策会让人疲倦,TOFU方法在这两者间实现了良好的平衡。如果从TOFU中去掉“信任”的部分,委托就无从谈起。如果去掉“首次使用”的部分,则用户将会很快因无休止的访问请求变得麻木。这种由安全系统造成的疲劳感会引起工作懈怠,这比安全系统原本要解决的安全问题更危险。
这种方法还可以让用户从功能而不是安全性的角度做出决策:“是否允许此客户端执行它请求的操作?”这是与传统安全模型的一个重要区别。在传统安全模型中,决策者需要提前限定哪些行为是不允许的。而这样的安全决策通常会令普通用户不知所措。无论如何,用户更关心的是他们想要完成哪些事情,而不是试图阻止哪些事情。
但这并不是说TOFU必须用于所有的事务或决策。实际上,安全架构师可以采用3层名单机制(如图1-9所示),它具有很强的灵活性。
由白名单确定已知的良好和受信任的应用,由黑名单确定已知的不良应用或者其他糟糕的参与者。这些决策很容易根据系统策略做出,而不需要最终用户参与。
在传统的安全模型中,讨论到这里就结束了,因为任何不在白名单里的内容会默认自动归入黑名单。然而,如果用上TOFU方法,就可以在上述的两个名单中间增加一个灰名单,在这个名单中,会优先考虑用户在运行时做出的信任决策。会有一定的策略来记录和审查这些用户决策,以使风险最小化。通过灰名单功能,系统的可扩展性得到了极大提升,同时又不牺牲安全性。
图 1-9 平行的信任分级