Skip to content

介绍

OAuth 是一个非常强大的工具,它的强大来自其灵活性,灵活性通常意味着它不仅能够完成你的构想,而且也会带来安全问题。
OAuth 管理 API 的访问权限,守护着重要数据,所以最关键的是避免反模式,运用最佳实践,以安全的方式使用它。
换句话说,虽然它的灵活性让你可以以任何方式使用和部署它,但并不意味着你应该那样随意。

OAuth 2.0 是什么,为什么要关心它

如果你从事 Web 软件开发,就应该听说过 OAuth。
它是一个安全协议,用于保护全球范围内大量且不断增长的 Web API,从 Facebook、Google 等大型服务商,到创业公司和各类企业内部的小型一次性 API。
它用于连接不同的网站,还支持原生应用和移动应用与云服务之间的连接。
它是各领域标准协议中的安全层,覆盖了从医疗到身份管理,从能源到社交网络的广阔应用领域。
OAuth 已成为当今 Web 上占主导地位的安全手段,它的无处不在为开发人员保护其应用铺平了道路

OAuth 2.0 是什么

OAuth 2.0 是一个授权协议,它允许软件应用代表(而不是充当)资源拥有者去访问资源拥有者的资源。
应用向资源拥有者请求授权,然后取得令牌(token),并用它来访问资源。
这一切都不需要应用去充当资源拥有者的身份,因为令牌明确表示了被授予的访问权。
从很多方面来说,你可以把 OAuth 令牌看作 Web 上的 "泊车钥匙"。
不是所有车都有泊车钥匙,但是对于有泊车钥匙的车来说,把泊车钥匙交给泊车员比直接交出常规钥匙更安全。
泊车钥匙限制泊车员只能操作点火开关和车门,而不能打开后备箱和手套箱。
更高级的泊车钥匙还能限制最高车速,甚至能在车辆行驶查过车主设定的距离后强制停车并向车主发出警报。
同样的道理,OAuth 令牌可以限制客户端只能执行资源拥有者授权的操作。

举个例子,假设你使用了一个照片云存储服务和一个云打印服务,并且想使用云打印服务来打印存放在云存储服务上的照片。
很幸运,这两个服务能够使用 API 来通信。
这很好,但两个服务由不同的公司提供,这意味着你在云存储服务商的账户和云打印服务商的账户没有关联。
使用 OAuth 可以解决这个问题: 授权云打印服务访问照片,但并不需要将存储服务上的账户密码交给他

虽然 OAuth 基本上不关心它所保护的资源类型,但它确实很适合当今的 RESTful Web 服务,也适用于 Web 应用和原生应用。
从小型单用户应用,到有数百万用户的互联网 API,它都适用。
在受控的企业环境中,它能对新一代内部业务 API 和系统访问进行管理,在它所成长起来的纷乱复杂的 Web 环境中,它也能游刃有余地保护各种面向用户的 API

众所周知,OAuth 是一个安全协议,但是它到底有什么用途呢?

OAuth 2.0 框架能让第三方应用以有限的权限访问 HTTP 服务,可以通过构建资源拥有者与 HTTP 服务间的许可交互机制,让第三方应用代表资源拥有者访问服务,或者通过授予权限给第三方应用,让其代表自己访问服务。

稍微解释一下: 作为一个授权框架,OAuth 关注的是如何让一个系统组件获取对另一个系统组件的访问权限。
OAuth 关注的是如何让一个系统组件获取对另一个系统组件的访问权限。
在 OAuth 的世界中,最常见的情形是客户端应用代表资源拥有者(通常是最终用户)访问受保护资源。
到目前为止,我们需要关心如下组件:

  • 资源拥有者 有权访问 API,并能将 API 访问权限委托出去。资源拥有者一般是能使用浏览器的人。
  • 受保护资源 是资源拥有者有权限访问的组件。这样的组件有多种形式,但大多数情况下是某种形式的 Web API。 虽然"资源"听起来就像是某种能下载的东西,但其实这些 API 支持读、写和其他操作
  • 客户端 是代表资源拥有者访问受保护资源的软件。在 OAuth 中,只要软件使用了受保护资源上的 API,它就是客户端。

黑暗的旧时代:凭据共享与凭据盗用

连接多个不同的服务并不是什么新鲜事,而且毫无疑问,从世上出现网络互联的服务开始,这种情况就存在了。

企业流行的做法是,复制用户的凭据并用它登录另一个服务

在这种情况下,照片打印服务要假定用户在照片存储服务上使用的凭据与在照片打印服务上的相同。
当用户登录照片打印服务后,该服务使用用户的用户名和密码登录照片存储网站,获取用户的账户访问权,假装用户。

在这种情况下,用户需要使用某种凭据与客户端进行身份认证,这些凭据通常是被集中控制的,并受客户端和受保护资源一致认可。
客户端先得到用户的用户名和密码或者会话 cookie,然后用它们访问受保护资源,假装是用户。
受保护资源将客户端视为用户并直接通过身份认证,而实际上与受保护资源建立连接的是客户端,正如前面所要求的那样。

这种方法要求用户在客户端和受保护资源使用相同的凭据,使得这种凭据盗用技术只能在同一安全域内使用。
也就是说,如果是一个公司控制着客户端、授权服务器和受保护资源,并且这些组件都在相同的策略和网络控制下运行,这种方法才行得通。
如果打印服务和存储服务是由同一个公司提供的,就能采用这种方法,因为用户可以在两个服务上使用相同的账户凭据。

这一技术还会将用户的密码暴露给客户端应用,即使在单一安全域中使用同一组凭据,这也基本上无法避免。
但无论如何,客户端是在扮演用户,受保护资源无法区分资源拥有者和扮演资源拥有者的客户端,因为二者都以同样的方式使用相同的用户名和密码。

但是,如果两个服务位于不同的安全域中,如照片打印例子中的情况,又会怎样呢?
不能再复制用户提供的用于登录当前应用的密码了,因为这个密码对于另一个应用来说是无效的。
对于这个问题,可以采取一种老套的手段来获取密码:向用户索要

如果打印服务想要获取用户的照片,它可以提示用户输入其照片存储网站上的用户名和密码。
然后就像前面那样,打印服务用这些凭据访问受保护资源,扮演用户。
在这种情况下,用户用于登录客户端的凭据和用于访问受保护资源的凭据可以不同。
不管怎么说,客户端通过向用户索要用于访问受保护资源的用户名和密码,解决了这个问题。
很多用户在实际中会允许这样的要求,特别是当使用受保护资源的是一个很有用的服务时。
因此,这仍然是当前移动应用通过用户账户访问后端服务的最常用方法之一:移动应用让用户输入用户名和密码,然后直接将这些凭据通过网络发送给后端API。
为了可以持续访问API,客户端应用会保存用户的凭据,以便在必要的时候用于访问受保护资源。
这种做法极其危险,因为一旦任何一个正在使用中的客户端被攻破,就意味着该用户在所有系统中的账户都被攻破。

在极少数场景下,这种方法还是可行的:客户端需要直接获得用户的凭据,并且能在用户不在场的情况下将这些凭据用于服务。
这排除了多种用户登录方式,包括几乎所有联合登录系统、很多多因素身份认证登录系统,以及大多数高安全等级的登录系统。

LDAP身份认证
有趣的是,这恰恰就是LDAP(lightweight directory access protocol,轻型目录访问协议)这样的密码身份认证技术使用的模式。
在使用LDAP进行身份认证的时候,客户端应用直接从用户那里获取凭据,然后通过LDAP服务器检验它们是否有效。
客户端系统在这个处理过程中必须得到用户的明文密码,否则无法向LDAP服务器验证密码的正确性。
从本质上来说,这就是针对用户的中间人攻击,虽然通常是善意的。

只要采用这种方法,就会将用户最重要的凭据暴露给可能并不可信的应用——客户端。
为了能一直充当用户,客户端就不得不以一种可重现的形式(通常是明文或者某种可逆的加密机制)存储用户的密码,用于后续访问受保护资源。
如果客户端应用被攻破,攻击者不仅能访问客户端,还能访问受保护资源以及用户使用的其他具有相同密码的服务。

而且,在以上的这些方法中,客户端应用充当资源拥有者,受保护资源无法分辨某个调用是由资源拥有者直接发起的,还是由客户端代发的。
这有何不妥呢?再回去看看打印服务的例子。
在少数情况下,大多数方法都可行,但考虑一下这种情形:你不希望打印服务能向存储服务中上传照片或删除其中的照片,而只能读取你要打印的照片,还希望它只能在需要打印的时候读取照片,并能随时解除其访问权限。

如果打印服务需要以你的身份访问照片,存储服务将无法辨别请求的发起者是你还是打印服务。
如果打印服务在背地里偷偷将你的密码保存下来(虽然它保证过不会这样做),那它就可以随时冒充你并窃取你的照片。
阻止这一流氓行为的唯一方法就是修改密码,让打印服务之前保存的密码失效。
更糟糕的是,很多用户都喜欢在不同系统中使用相同的密码,这可能导致所有关联账户都受到牵连。
坦率地说,为了解决多个服务连接的问题,我们引发了更严重的问题。

现在你已经看到,复制用户密码并不是一个好方法。
如果授予打印服务全局的访问权限,使它能代表由它指定的任何用户并访问存储服务上的所有照片,又会是怎样的情况呢?
常用的方式是为客户端颁发一个开发者密钥,让客户端使用该密钥直接调用受保护资源。

在这种方法中,开发者密钥是一种全局的密钥,客户端可以用它来充当任意一个由其指定的用户,用户的指定很可能通过一个API参数来完成。
这样做的好处是避免了向客户端暴露用户凭据,但代价是要向客户端提供功能强大的开发者密钥。
有了这种密钥,打印服务随时都能任意地打印所有用户的所有照片,因为它实际上拥有了自由访问受保护资源的权力。
这在一定程度上是可行的,但前提是受保护资源要充分了解并信任客户端。
但是这样的关系几乎不可能存在于两个组织之间,例如照片打印例子中的两个服务。
此外,如果客户端的密钥被盗,将对受保护资源造成灾难性的损害,因为存储服务的所有用户都会受到影响,无论他们是否使用打印服务。

还有一个方法是给用户一个特殊的密码,此密码仅用于透露给第三方服务。
用户自己不会使用这个密码来登录,只是将它粘贴到所使用的第三方应用里。
这听起来很像最开始提到的那种功能有限的泊车钥匙。

现在,距离理想的系统又近了一步,因为用户不再需要向客户端透露登录密码,受保护资源也不再需要相信客户端时刻都能代表所有用户执行正确的操作。
但是,这种系统的可用性并不好。它要求用户除了管理自己的主密码之外,还要创建、分发和管理特殊的凭据。
因为需要用户来管理这些凭据,所以一般来说,客户端与凭据本身并没有对应关系。这使得撤销某个具体应用的访问权限变得很困难。

还有更好的办法吗?

如果能为每个客户端和每个用户的组合分别颁发这种对受保护资源具有受限访问权限的凭据,会怎样?
如此一来,就可以将受限访问权限分别与这些受限的凭据绑定。
更进一步,如果有一个基于网络的协议,能够部署到整个互联网上,跨安全边界地生成并安全分发这些受限的凭据,同时具有良好的用户体验,又会怎样?

授权访问

OAuth协议的设计目的是:让最终用户通过OAuth将他们在受保护资源上的部分权限委托给客户端应用,使客户端应用代表他们执行操作。
为实现这一点,OAuth在系统中引入了另外一个组件:授权服务器

受保护资源依赖授权服务器向客户端颁发专用的安全凭据——OAuth访问令牌。
为了获取令牌,客户端首先将资源拥有者引导至授权服务器,请求资源拥有者为其授权。
授权服务器先对资源拥有者进行身份认证,然后一般会让资源拥有者选择是否对客户端授权。
客户端可以请求授权功能或权限范围的子集,该子集可能会被资源拥有者进一步缩小。
一旦授权请求被许可,客户端就可以向授权服务器请求访问令牌。
按照资源拥有者的许可,客户端可以使用该令牌对受保护资源上的API进行访问

在这个过程中,没有将资源拥有者的凭据暴露给客户端:资源拥有者向授权服务器进行身份认证的过程中所用的信息是独立于客户端交互的。
客户端没有功能强大的开发者密钥,无法随意访问任何资源,而是必须在得到有效的资源拥有者授权之后才能访问受保护资源。
虽然大多数OAuth客户端可以向授权服务器进行身份认证,但仍然需要得到授权后才能访问资源。

用户通常不必查看或者直接处理访问令牌。OAuth不需要由用户生成令牌并粘贴到客户端,而是简化了这一过程:客户端请求令牌,用户对客户端授权。
然后由客户端管理令牌,用户管理客户端应用。

以上是对OAuth工作原理的一般性概述,但实际上OAuth拥有多种获取访问令牌的方法