连接多个不同的服务并不是什么新鲜事,而且毫无疑问,从世上出现网络互联的服务开始,这种情况就存在了。
企业流行的做法是, 复制用户的凭据并用它登录另一个服务 (如图1-3所示)。在这种情况下,照片打印服务要假定用户在照片存储服务上使用的凭据与在照片打印服务上的相同。当用户登录照片打印服务后,该服务使用用户的用户名和密码登录照片存储网站,获取用户的账户访问权,假装用户。
图 1-3 不征求同意就复制资源拥有者的凭据
在这种情况下,用户需要使用某种凭据与客户端进行身份认证,这些凭据通常是被集中控制的,并受客户端和受保护资源一致认可。客户端先得到用户的用户名和密码或者会话cookie,然后用它们访问受保护资源,假装是用户。受保护资源将客户端视为用户并直接通过身份认证,而实际上与受保护资源建立连接的是客户端,正如前面所要求的那样。
这种方法要求用户在客户端和受保护资源端使用相同的凭据,使得这种凭据盗用技术只能在同一安全域内使用。也就是说,如果是一个公司控制着客户端、授权服务器和受保护资源,并且这些组件都在相同的策略和网络控制下运行,这种方法才行得通。如果打印服务和存储服务是由同一个公司提供的,就能采用这种方法,因为用户可以在两个服务上使用相同的账户凭据。
这一技术还会将用户的密码暴露给客户端应用,即使在单一安全域中使用同一组凭据,这也基本上无法避免。但无论如何,客户端是在 扮演 用户,受保护资源无法区分资源拥有者和扮演资源拥有者的客户端,因为二者都以同样的方式使用相同的用户名和密码。
但是,如果两个服务位于不同的安全域中,如照片打印例子中的情况,又会怎样呢?不能再复制用户提供的用于登录当前应用的密码了,因为这个密码对于另一个应用来说是无效的。对于这个问题,可以采取一种老套的手段来获取密码: 向用户索要 (如图1-4所示)。
图 1-4 向资源拥有者索要凭据并用于访问受保护资源
如果打印服务想要获取用户的照片,它可以提示用户输入其照片存储网站上的用户名和密码。然后就像前面那样,打印服务用这些凭据访问受保护资源,扮演用户。在这种情况下,用户用于登录客户端的凭据和用于访问受保护资源的凭据可以不同。不管怎么说,客户端通过向用户索要用于访问受保护资源的用户名和密码,解决了这个问题。 很多用户在实际中会允许这样的要求 ,特别是当使用受保护资源的是一个很有用的服务时。因此,这仍然是当前移动应用通过用户账户访问后端服务的最常用方法之一:移动应用让用户输入用户名和密码,然后直接将这些凭据通过网络发送给后端API。为了可以持续访问API,客户端应用会保存用户的凭据,以便在必要的时候用于访问受保护资源。这种做法极其危险,因为一旦任何一个正在使用中的客户端被攻破,就意味着该用户在所有系统中的账户都被攻破。
在极少数场景下,这种方法还是可行的:客户端需要直接获得用户的凭据,并且能在用户不在场的情况下将这些凭据用于服务。这排除了多种用户登录方式,包括几乎所有联合登录系统、很多多因素身份认证登录系统,以及大多数高安全等级的登录系统。
LDAP身份认证
有趣的是,这恰恰就是LDAP(lightweight directory access protocol,轻型目录访问协议)这样的密码身份认证技术使用的模式。在使用LDAP进行身份认证的时候,客户端应用直接从用户那里获取凭据,然后通过LDAP服务器检验它们是否有效。客户端系统在这个处理过程中必须得到用户的明文密码,否则无法向LDAP服务器验证密码的正确性。从本质上来说,这就是针对用户的中间人攻击,虽然通常是善意的。
只要采用这种方法,就会将用户最重要的凭据暴露给可能并不可信的应用——客户端。为了能一直充当用户,客户端就不得不以一种可重现的形式(通常是明文或者某种可逆的加密机制)存储用户的密码,用于后续访问受保护资源。如果客户端应用被攻破,攻击者不仅能访问客户端,还能访问受保护资源以及用户使用的其他具有相同密码的服务。
而且,在以上的这些方法中,客户端应用 充当 资源拥有者,受保护资源无法分辨某个调用是由资源拥有者直接发起的,还是由客户端代发的。这有何不妥呢?再回去看看打印服务的例子。在少数情况下,大多数方法都可行,但考虑一下这种情形:你不希望打印服务能向存储服务中上传照片或删除其中的照片,而只能读取你要打印的照片,还希望它只能在需要打印的时候读取照片,并能随时解除其访问权限。
如果打印服务需要以你的身份访问照片,存储服务将无法辨别请求的发起者是你还是打印服务。如果打印服务在背地里偷偷将你的密码保存下来(虽然它保证过不会这样做),那它就可以随时冒充你并窃取你的照片。阻止这一流氓行为的唯一方法就是修改密码,让打印服务之前保存的密码失效。更糟糕的是,很多用户都喜欢在不同系统中使用相同的密码,这可能导致所有关联账户都受到牵连。坦率地说,为了解决多个服务连接的问题,我们引发了更严重的问题。
现在你已经看到,复制用户密码并不是一个好方法。如果授予打印服务全局的访问权限,使它能代表由它指定的任何用户并访问存储服务上的所有照片,又会是怎样的情况呢?常用的方式是为客户端颁发一个 开发者密钥 (如图1-5所示),让客户端使用该密钥直接调用受保护资源。
图 1-5 使用全局的开发者密钥,确定你宣称代表的用户
在这种方法中,开发者密钥是一种全局的密钥,客户端可以用它来充当任意一个由其指定的用户,用户的指定很可能通过一个API参数来完成。这样做的好处是避免了向客户端暴露用户凭据,但代价是要向客户端提供功能强大的开发者密钥。有了这种密钥,打印服务随时都能任意地打印所有用户的所有照片,因为它实际上拥有了自由访问受保护资源的权力。这在一定程度上是可行的,但前提是受保护资源要充分了解并信任客户端。但是这样的关系几乎不可能存在于两个组织之间,例如照片打印例子中的两个服务。此外,如果客户端的密钥被盗,将对受保护资源造成灾难性的损害,因为存储服务的所有用户都会受到影响,无论他们是否使用打印服务。
还有一个方法是 给用户一个特殊的密码 (如图1-6所示),此密码仅用于透露给第三方服务。用户自己不会使用这个密码来登录,只是将它粘贴到所使用的第三方应用里。这听起来很像本章最开始提到的那种功能有限的泊车钥匙。
图 1-6 针对具体服务且访问受限的密码
现在,距离理想的系统又近了一步,因为用户不再需要向客户端透露登录密码,受保护资源也不再需要相信客户端时刻都能代表所有用户执行正确的操作。但是,这种系统的可用性并不好。它要求用户除了管理自己的主密码之外,还要创建、分发和管理特殊的凭据。因为需要用户来管理这些凭据,所以一般来说,客户端与凭据本身并没有对应关系。这使得撤销某个具体应用的访问权限变得很困难。
还有更好的办法吗?
如果能为每个客户端和每个用户的组合分别颁发这种对受保护资源具有受限访问权限的凭据,会怎样?如此一来,就可以将受限访问权限分别与这些受限的凭据绑定。更进一步,如果有一个基于网络的协议,能够部署到整个互联网上,跨安全边界地生成并安全分发这些受限的凭据,同时具有良好的用户体验,又会怎样?接下来就开始讨论这一有趣的话题了。