跳转至

14.基于 OAuth 2.0 的协议与配置文件

本章内容包括

  • 用户管理访问(UMA):构建在 OAuth 2.0 之上的协议,用于实现动态同意与策略管理
  • 健康关系信任(HEART):面向医疗健康场景的配置文件,基于 OAuth 2.0、OpenID Connect(OIDC)与 UMA
  • 国际政府(iGov):面向政务服务的配置文件,基于 OAuth 2.0 与 OpenID Connect

到目前为止你已经看到,OAuth 2.0 是一个非常强大的协议,并且在它所擅长的事情上表现出色:委派访问权限,并通过 HTTP 传递这种授权信息。但仅靠 OAuth 本身并不能包办一切。如果你的需求超出了 OAuth 的能力边界,它依然是工具箱里非常有价值的一件工具,但它并不是你手里唯一的选择。OAuth 是构建更复杂系统时的一块通用积木。

在「使用 OAuth 2.0 进行用户认证」中,我们讨论了一个重要用例——用户认证,以及一个构建在 OAuth 之上、专门用于完成该功能的标准协议:OpenID Connect。本章将进一步介绍若干基于 OAuth 2.0 的协议与配置文件,它们在这一坚实基础上扩展出更高级的能力。我们会先看一种对 OAuth 的应用扩展:支持用户之间的共享与动态同意管理。随后再介绍两项针对特定领域对 OAuth 及相关协议进行“配置化”的工作,并讨论它们与更广泛生态的关系。需要特别说明的是:在本书写作时,这些规范仍在快速演进,你读到本章时最终版本(或当前版本)很可能已经有所不同。我们也应当指出,本书作者之一深度参与了这三项标准化与配置文件制定工作。

用户管理访问(UMA)

UMA1 是构建在 OAuth 2.0 之上的协议,它允许资源所有者通过其自行选择的授权服务器,对资源访问进行更精细的控制——无论访问方是资源所有者自己控制的软件,还是由其他用户控制的软件。UMA 协议在 OAuth 2.0 之上主要扩展了两项能力:用户到用户的委派,以及在单个资源服务器上支持多个授权服务器的处理机制。

换句话说,OAuth 2.0 允许资源所有者把权限委托给客户端软件,让它代表自己行事;而 UMA 则允许资源所有者把权限委托给另一位用户的客户端软件,让它代表那位用户行事。通俗地说,OAuth 实现的是 Alice-to-Alice 的共享(因为客户端是 Alice 自己在运行),而 UMA 实现的是 Alice-to-Bob 的共享。UMA 还允许 Alice 自带授权服务器,并把它介绍给资源服务器。Bob 的客户端一旦尝试访问 Alice 的资源,就能发现 Alice 的授权服务器。

UMA 之所以能做到这一点,是因为它调整了传统 OAuth 各角色之间的关系,并在此过程中引入了一个全新的角色:请求方(RqP)。2 资源所有者负责管理授权服务器与资源服务器之间的关系,配置策略以允许第三方访问资源。由请求方控制的客户端可以通过提交自身以及请求方的相关信息来申请访问令牌,以满足资源所有者设定的要求。资源所有者完全不与客户端交互,而是把访问权限委托给请求方(见图 14.1)。


图 14.1 UMA 协议的组成部分

没错,UMA 这套“舞步”确实比我们在「OAuth 之舞」讲的 OAuth 更复杂一些,但这是因为它要解决的问题本身就更复杂。UMA 的保护 API 部分由资源所有者主导,而 UMA 的授权 API 部分则由请求方主导。各方参与者和组件在 UMA 的流程中都各司其职。

为什么 UMA 很重要

在深入了解 UMA 的工作机制之前,先说说你为什么需要关注它。UMA 在“用户对用户共享”和“用户可控的授权服务器”方面的能力,几乎让它在当今互联网安全协议领域里独树一帜。虽然这也使得 UMA 成为一个多参与方、多步骤、环节繁多的复杂协议,但它因此具备强大的能力,能够解决其他技术难以触及的问题。

为了更直观一点,我们回到照片打印的例子。假设不再是 Alice 使用第三方服务打印自己的照片,而是 Alice 最好的朋友 Bob 想打印一些两人共同的演唱会照片——这些照片存放在 Alice 的账号里。首先,Alice 可以让她的照片打印服务使用她自己的个人授权服务器。这样一来,Alice 就能在自己的授权服务器上配置一条策略,大意是:“Bob 来了之后,他可以读取这些照片中的任意一张。”这种访问控制并不罕见,但在 UMA 中,授予 Bob 的权限还会延伸到 Bob 代表自己运行的软件。在这个场景里,Bob 在某个云打印服务上也有自己的账号,并将该服务指向 Alice 的照片。随后,Alice 的授权服务器会要求 Bob 通过一组声明(claims)来证明自己的身份。只要 Bob 能提供的内容满足 Alice 策略的要求,Bob 的打印服务就能访问 Alice 分享给他的照片,全程无需 Bob 冒充 Alice。Bob 也不一定需要能够登录 Alice 的授权服务器,更不需要在 Alice 的照片分享网站上注册账号。

通过这种机制,即使资源所有者在请求发生时不在场,也可以允许请求方访问资源。只要客户端能够以某种方式满足资源策略中的要求,它就可以代表请求方获取令牌。这个令牌在资源服务器上的使用方式与普通 OAuth 访问令牌没有区别,只是现在资源服务器能够看到从资源所有者到请求方再到客户端的完整委托链,并据此做出授权决策。

虽然 UMA 也可以在一个“所有参与方彼此都已相互了解”的静态世界中运作,但它同样允许在运行时由已授权的参与方引入新的组件。通过允许资源所有者自带授权服务器,UMA 为真正以用户为中心的信息经济奠定了基础:终端用户不仅能决定哪些服务可以代表自己行动,还能决定哪些其他参与方——无论是用户还是软件——可以访问他们的数据。

UMA 还定义了一种机制,允许资源服务器在授权服务器上注册其所保护资源的引用。这些引用被称为资源集(resource sets),用于表示一组受保护资源的集合包,可以附加到策略上并供客户端访问。比如,Alice 的照片服务可以为 Alice 的度假照片注册一个资源集,为 Alice 的私密照片再注册一个资源集,还可以为 Alice 的整体账户信息注册另一个资源集。每个资源集都可以绑定各自独立的策略,让 Alice 决定谁可以使用哪一部分信息。资源集注册协议大体上类似于我们在「动态客户端注册」介绍的动态客户端注册协议。更有意思的是,动态客户端注册在某种程度上也可以追溯到 UMA:在 UMA 的场景里,“在运行时把新客户端引入授权服务器”这一问题更加迫切。

如果说 OAuth 是通过让终端用户在生态系统内做运行时决策,推动了“可接受的安全策略”边界,那么 UMA 则是在推动“这个生态系统的边界”本身。策略允许的范围总是落后于技术能力,但 UMA 展现出来的能力正形成强大的吸引力,也开始推动关于“安全到底能做到什么程度”的讨论向前发展。

UMA 协议如何工作

下面我们从头到尾看一笔具体的 UMA 协议交互流程。正如你在「真实世界中的 OAuth 2.0」看到的,OAuth 是一个包含大量选项与扩展的协议。作为构建在 OAuth 之上的协议,UMA 继承了这些可选项,并在此基础上增加了更多自己的扩展。要深入讲清楚它,很容易需要花上好几章,甚至一本书的篇幅。3 虽然我们没有足够的时间和空间来彻底展开这个复杂协议,但至少可以在这里给出一个相对完整的概览。在这个例子里,我们假设是一次完全的“冷启动”:各个服务事先互不认识,所有组件都需要彼此“介绍”才能协同工作。我们会在需要的时候使用服务发现(service discovery)和动态客户端注册来完成组件间的互相引入,而不是依赖手工注册。对于 UMA 中采用较传统 OAuth 令牌的部分,我们将使用「OAuth 之舞」深入讲解过的授权码(authorization code)流程。为便于说明和理解,我们还会对流程中的一些环节做简化:略过部分细节,并跳过协议中一些规范描述不够明确或仍在工作组积极评审的部分。最后,尽管 UMA 1.0 已经定稿,但该规范仍在社区推动下持续演进,因此这里给出的许多具体示例(以及部分架构层面的假设)在未来版本中未必仍然适用。在这些前提下,整体的 UMA “舞步”大致如图 14.2 所示。


图 14.2 UMA 协议详解

让我们更详细地梳理一下图 14.2 中各个关键步骤。下面每段都按编号标注,对应序列图中的相同部分。

  • 1.资源所有者将资源服务器引荐给授权服务器。UMA 协议并不规定这一步具体如何完成,但提供了一些可能的实现方式。在一个紧密耦合的 UMA 生态中,资源所有者可能可以从列表中选择授权服务器。在更开放、分布更广的环境(比如互联网)里,资源所有者可以把自己的 WebFinger ID 提供给受保护资源,以便发现其个人授权服务器——这与「使用 OAuth 2.0 进行用户认证」中身份服务器发现的过程类似。无论采用哪种方式,最终资源服务器会拿到授权服务器的一个 URL,也就是它的发行者 URL(issuer URL)。

  • 2.资源服务器发现授权服务器的配置,并注册为一个 OAuth 客户端。与「使用 OAuth 2.0 进行用户认证」介绍的 OpenID Connect 类似,UMA 也提供了服务发现协议,允许系统中的其他组件获取授权服务器的关键信息。这些发现信息托管在一个基于授权服务器发行者 URL 的地址上,在其后追加 /.well-known/uma--configuration。返回内容是一个 JSON 文档,包含 UMA 授权服务器的相关信息。

{
  "version":"1.0",
  "issuer":"https://example.com",
  "pat_profiles_supported":["bearer"],
  "aat_profiles_supported":["bearer"],
  "rpt_profiles_supported":
  ["https://docs.kantarainitiative.org/uma/profiles/uma-token-bearer-1.0"],
  "pat_grant_types_supported":["authorization_code"],
  "aat_grant_types_supported":["authorization_code"],

"claim_token_profiles_supported":["https://example.com/claims/formats/token1"],
  "dynamic_client_endpoint":"https://as.example.com/dyn_client_reg_uri",
  "token_endpoint":"https://as.example.com/token_uri",
  "authorization_endpoint":"https://as.example.com/authz_uri",

"requesting_party_claims_endpoint":"https://as.example.com/rqp_claims_uri"  ,

"resource_set_registration_endpoint":"https://as.example.com/rs/rsrc_uri",
  "introspection_endpoint":"https://as.example.com/rs/status_uri",
  "permission_registration_endpoint":"https://as.example.com/rs/perm_uri",
  "rpt_endpoint":"https://as.example.com/client/rpt_uri"
}

这些信息包括OAuth 交互所需的授权端点和令牌端点等内容,也包括 UMA 的专有信息,例如资源集应当在哪里注册(这会在后续步骤中用到)。需要注意的是,和 OAuth、OpenID Connect 一样,UMA 要求使用 TLS 来保护整个协议过程中的所有 HTTP 交互。随后,资源服务器可以通过动态客户端注册将自己注册为 OAuth 客户端——我们在「动态客户端注册」已经详细介绍过——也可以通过某种带外的静态流程完成注册。归根结底,它和其他 OAuth 客户端并无不同;这一步唯一与 UMA 相关的点在于,资源服务器必须能够获取带有 uma_protection 特殊 scope 的令牌。这些令牌将在接下来的步骤中用于访问授权服务器上的特定功能。

  • 3.资源所有者对资源服务器进行授权。既然资源服务器此时以 OAuth 客户端的身份在工作,它就需要像其他任何 OAuth 客户端一样获得资源所有者的授权。与 OAuth 一样,实现方式有很多,最终都会让资源服务器拿到具备相应权限的访问令牌;但由于这是直接代表资源所有者执行的操作,通常会采用交互式的 OAuth 授权类型来完成,例如授权码流程。资源服务器通过该过程获得的访问令牌称为保护 API 令牌(Protection API Token,PAT)。PAT 至少需要包含 uma_protection 的 scope,但也可以同时绑定其他 scope。资源服务器使用 PAT 来管理受保护资源、申请权限票据(permission ticket)以及对令牌进行自省(introspection)。这些操作统称为保护 API,由授权服务器提供。此时需要意识到一个既重要又容易让人困惑的事实:受保护资源现在扮演的是 OAuth 客户端的角色,而授权服务器则通过其保护 API 扮演了受保护资源的角色。不过别忘了,这并非不可理解,因为在 OAuth 生态中,每个组件本质上都是一组“角色”,可以在不同时间由不同的软件来扮演。同一套软件也完全可能同时充当客户端和受保护资源,例如这取决于上层 API 想要达成的目标是什么。

  • 4.资源服务器会将其资源集注册到授权服务器。现在需要将资源服务器代表资源所有者所保护的资源告知授权服务器。资源服务器通过一种类似于动态客户端注册协议的机制来注册其资源集。资源服务器使用 PAT 作为授权,向资源集注册 URI 发送 HTTP POST 请求,提交其希望保护的每个资源集的详细信息。

POST /rs/resource_set HTTP/1.1
Content-Type: application/json
Authorization: Bearer MHg3OUZEQkZBMjcx

{
  "name" : "Tweedl Social Service",
  "icon_uri" : "http://www.example.com/icons/sharesocial.png",
  "scopes" : [
    "read-public",
    "post-updates",
    "read-private",
    "http://www.example.com/scopes/all"
  ],
  "type" : "http://www.example.com/rsets/socialstream/140-compatible"
}

这些信息包括显示名称、图标,更重要的是与该资源集关联的一组 OAuth Scope。授权服务器会为该资源集分配一个唯一标识符,并连同一个 URL 一并返回;资源服务器可以通过该 URL 引导资源所有者以交互方式管理与该资源集相关的策略。

1
2
3
4
5
6
7
8
HTTP/1.1 201 Created
Content-Type: application/json
Location: /rs/resource_set/12345

{
  "_id" : "12345",
  "user_access_policy_uri" : "http://as.example.com/rs/222/resource/333/policy"
}

Location 头包含一个 URL,用于通过 RESTful API 模式管理资源集注册本身。资源服务器可在使用 POST 的同时,配合 HTTP 的 GET、PUT 和 DELETE 方法分别读取、更新和删除其资源集。

  • 5.资源所有者在授权服务器上为资源集制定策略。 此时资源集已经注册完成,但还没有说明它应当如何被访问。在任何客户端请求访问该资源集之前,资源所有者需要为资源配置策略,用于表明谁可以访问该资源,以及在什么条件下可以访问。UMA 完全不规定这些策略的具体形态,因为策略引擎的实现与配置方式几乎可以千变万化。常见的策略选项包括日期范围、用户标识符,或对资源可被访问次数的限制。每条策略还可以与该资源集所支持的部分 scope 关联起来,使资源所有者能够以更丰富的方式表达其共享意图。例如,资源所有者可以决定:任何使用其家庭域名邮箱的用户都可以读取其所有照片,但只有特定指定的个人才能上传新照片。归根结底,请求方及其客户端需要能够提供一组 claims,以满足已配置策略所要求的条件。需要特别强调的是:没有配置任何策略的资源集必须被视为不可访问,直到设置了合适的策略为止。该限制可防止粗心的授权服务器以“开放”状态失效,从而导致资源在无意中对任何提出请求的人都可用。毕竟,如果某个资源没有设置任何必需的 claims,那是否意味着我无需提交任何 claims 就能满足所有策略并拿到 token 呢?4

策略一旦配置完成,资源所有者通常就不再介入,接下来由请求方开始 UMA 流程的“下半场”。当然,也有可能授权服务器配备了更高级的运行时策略引擎:当其他人(即请求方)尝试访问其资源时,可以即时提示资源所有者进行授权。不过,我们这里不展示这种机制。

  • 6.请求方指示客户端访问资源服务器。这一步类似于常规 OAuth 交互的起点,即资源所有者让其客户端代表自己去访问某些资源。与 OAuth 一样,客户端如何获知受保护资源的 URL,或获取访问该受保护 API 所需的信息,不在本规范的范围之内。不同于 OAuth 的是,请求方是在指示客户端应用去访问由他人控制的资源,而客户端可能并不知道与之对应的授权服务器在哪里。

  • 7.客户端请求受保护的资源。客户端在发起请求时,由于缺少访问该资源所需的充分授权而开始这一流程。最常见的情况是,请求中没有携带访问令牌(access token)。

GET /album/photo.jpg HTTP/1.1
Host: photoz.example.com

在「构建一个简单的 OAuth 受保护资源」讨论不同的作用域模式时,我们看到 OAuth 之所以能用于保护多种风格的 API,是因为访问令牌会为请求提供额外的上下文信息,例如资源所有者的标识符或与之关联的作用域。这使得受保护资源能够根据访问令牌所携带的数据返回不同的信息:比如同一个 URL 下返回不同用户的信息,或根据令牌的作用域以及授权该令牌的用户,只返回信息的某些子集。在 OpenID Connect 中,OAuth 的这一特性让 UserInfo 端点可以用一个统一的 URL 来为服务器上的所有身份提供信息,而无需提前向客户端泄露用户标识符,正如我们在「使用 OAuth 2.0 进行用户认证」所看到的。

在 UMA 中,资源服务器必须能够从这次初始 HTTP 请求的上下文判断:客户端请求试图访问的是哪个资源集,从而确定所代表的是哪个资源所有者以及哪个授权服务器。由于我们没有访问令牌来辅助做出这个决定,只能查看 URL、请求头以及 HTTP 请求的其他部分。这个限制实际上将 UMA 可用于保护的 API 类型限定为那些能够根据 URL 及其他 HTTP 信息来区分资源的 API。

  • 8.资源服务器向授权服务器申请一个权限票据,用于表示所请求的访问,并将该票据交给客户端。 一旦资源服务器明确该请求试图访问的资源集,从而确定与该资源集关联的授权服务器后,资源服务器会向授权服务器的权限票据注册端点发送一条 HTTP POST 请求,以申请一个用于表示该访问请求的权限票据。该请求包含资源集的标识符,以及一组资源服务器认为适合用于访问的 scope,并由 PAT 授权。该请求中的 scope 可以是资源集所定义 scope 的子集,使资源服务器能够在其认为合适的范围内限制客户端的访问。当然,客户端可能具备比这次初始请求表面所体现的更多操作能力,但资源服务器无法事先推断这一点。
POST /tickets HTTP/1.1
Content-Type: application/json
Host: as.example.com
Authorization: Bearer 204c69636b6c69

{
  "resource_set_id": "112210f47de98100",
  "scopes": [
      "http://photoz.example.com/dev/actions/view",
      "http://photoz.example.com/dev/actions/all"
  ]
}

授权服务器会确保 PAT 对应的正是最初注册该资源集的同一个资源服务器,并且所请求的所有 scope 都在该资源集上可用。随后,授权服务器会创建并签发一个权限票据(permission ticket),以简单 JSON 对象中的字符串形式返回给资源服务器。

1
2
3
4
5
6
HTTP/1.1 201 Created
Content-Type: application/json

{
"ticket": "016f84e8-f9b9-11e0-bd6f-0021cc6004de"
}

资源服务器无需保存这些票据的任何引用,也不必负责管理它们,因为它们只是客户端在整个 UMA 流程中与授权服务器交互的凭据句柄。授权服务器会在需要时自动使其过期并将其吊销。

  • 9.资源服务器会将票据返回给客户端,并同时提供指向授权服务器的地址。拿到票据后,资源服务器就可以最终响应客户端的请求。资源服务器通过特殊的 WWW-Authenticate: UMA 头,将票据以及用于保护该资源的授权服务器的发行者(issuer)URL 一并提供给客户端。
1
2
3
4
HTTP/1.1 401 Unauthorized
WWW-Authenticate: UMA realm="example",
  as_uri="https://as.example.com",
  ticket="016f84e8-f9b9-11e0-bd6f-0021cc6004de"

此响应中唯一由 UMA 协议强制规定的部分是头部,其余内容(包括状态码、响应体以及其他头部)都由受保护资源自行决定。这样一来,资源服务器既可以在返回指示信息、告知客户端如何获取更高访问级别的同时,也照常提供公开可用的信息。或者,如果客户端在初始请求中确实带了访问令牌,但该令牌未包含其可用的全部访问权限,资源服务器就可以根据客户端当前提供的访问级别返回相应内容,并提示客户端可以尝试提升访问级别。在我们的示例中,客户端没有发送令牌,且该 API 不提供任何公开信息,因此服务器在返回该头部的同时,返回了 HTTP 401 状态码。

  • 10.客户端会发现授权服务器的配置并向其完成注册。 与资源服务器类似,客户端需要先确认授权服务器的位置,以及在后续步骤中该如何与之交互。由于这里的流程是并行进行的,我们不再重复其细节。流程结束后,客户端会获得一套用于与授权服务器交互的专属凭据,这些凭据与访问受保护资源所使用的凭据相互独立。

获取令牌之前还需要先有令牌吗?

在 UMA 1.0 中,客户端还需要获取一个 OAuth 访问令牌,称为授权访问令牌(Authorization Access Token,AAT)。这个令牌的目的,是将请求方(RqP)与客户端及授权服务器绑定起来,其作用与系统另一侧的 PAT 类似。不过,由于在流程后续步骤中,RqP 可能需要以交互方式提交声明(claims),这种绑定并非严格必要。

此外,要为 AAT 进行授权,RqP 必须能够登录授权服务器,并授权客户端以一个特殊 scope(uma_authorization)获取令牌。但 RqP 并不一定与授权服务器存在任何关系——通常只有资源所有者与其有关联——因此也不能指望 RqP 能走完常规的 OAuth 交互流程。基于这些以及其他原因,UMA 协议的后续版本可能会取消 AAT,我们在讨论中也尽量降低了它的重要性。未来版本的 UMA 协议也可能采用不同机制,在流程的某个环节表达并携带 RqP 的同意(consent)。

  • 11.客户端将票据提交给授权服务器以获取访问令牌。这个过程类似于在授权码授权(Authorization Code Grant)中客户端提交授权码,但这里使用的是从资源服务器获取的票据作为临时、受限的凭证。客户端会向授权服务器发送一条 HTTP POST 请求,并将票据作为参数携带。
1
2
3
4
5
6
7
POST /rpt_uri HTTP/1.1
Host: as.example.com
Authorization: Bearer jwfLG53^sad$#f

{
"ticket": "016f84e8-f9b9-11e0-bd6f-0021cc6004de"
}

授权服务器会检查票据,以确定该请求关联的是哪个资源集。一旦识别出资源集,授权服务器就能确定与该资源集对应的策略,从而明确客户端为了获取访问令牌需要提交哪些声明。在我们的示例中,由于票据刚刚创建,授权服务器判断该票据所关联的声明不足以满足这些策略要求。于是,授权服务器向客户端返回一个错误响应,提示客户端需要收集相应的声明,并向授权服务器证明:请求方以及客户端自身都应被允许访问。

HTTP/1.1 403 Forbidden
Content-Type: application/json
Cache-Control: no-store

{
 "error": "need_info",
 "error_details": {
   "authentication_context": {
     "required_acr": ["https://example.com/acrs/LOA3.14159"]
   },
   "requesting_party_claims": {
     "required_claims": [
       {
         "name": "email23423453ou453",
         "friendly_name": "email",
         "claim_type": "urn:oid:0.9.2342.19200300.100.1.3",
         "claim_token_format":
["http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken"],
         "issuer": ["https://example.com/idp"]
       }
     ],
     "redirect_user": true,
     "ticket": "016f84e8-f9b9-11e0-bd6f-0021cc6004de"
   }
 }
}

此示例响应包含一组提示,说明客户端可能需要哪些类型的声明以及应从何处获取这些声明;在本例中,这些声明来自所给 OpenID Connect 签发方提供的 OpenID Connect 声明。

  • 12.客户端收集相关声明(claims)并将其提交给授权服务器。在这个阶段,客户端可以通过多种方式向授权服务器提供其所要求的信息。UMA 协议刻意对声明收集(claims-gathering)流程的细节保持一定的模糊性,以便适配各种不同的场景和使用条件。如果客户端已经持有所需声明,并且其格式能够被授权服务器验证,那么客户端就可以在后续的 token 请求中直接携带这些声明并发送。
POST /rpt_authorization HTTP/1.1
Host: www.example.com
Authorization: Bearer jwfLG53^sad$#f

{
    "rpt": "sbjsbhs(/SSJHBSUSSJHVhjsgvhsgvshgsv",
    "ticket": "016f84e8-f9b9-11e0-bd6f-0021cc6004de",
    "claim_tokens": [
      {
        "format":
"http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken",
        "token": "..."
      }
    ]
}

这种方法在客户端声明与其自身或部署该客户端软件的组织相关的主张时非常有效。权威机构可以对这类主张进行签名,使授权服务器能够直接对其进行验证与校验。若客户端需要提交关于请求方的信息,这种方法就不那么有用了,因为请求方与客户端之间的关系并不明确;不过,如果客户端与授权服务器之间存在强信任关系,即便如此也仍然可行。相反,如果客户端需要让请求方自行提交主张(例如其身份信息),则客户端会将请求方重定向到授权服务器的主张收集端点。客户端会携带自身的客户端 ID、ticket 值,以及在主张收集完成后用于跳转的重定向 URI。

1
2
3
HTTP/1.2 302 Found
Location: https://as.example.com/rqp_claims?client_id=some_client_id&state=abc
&claims_redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fredirect_claims&ticket=016f84e8-f9b9-11e0-bd6f-0021cc6004de

在这个端点上,请求方可以直接与授权服务器交互,提交所需的声明(claims)。UMA 规范对这一流程同样没有做强制规定,留给实现方自行定义;但在我们的示例中,请求方将使用其 OpenID Connect 账号登录授权服务器。此时,UMA 授权服务器充当 OpenID Connect 的信赖方(Relying Party)5 ,从而获得对请求方身份信息的访问权限,并可利用这些信息来满足策略(policy)请求。

当授权服务器在声明收集流程中获得满足后,会将请求方重定向回客户端,以告知流程可以继续进行。

1
2
3
HTTP/1.1 302 Found
Location: https://client.example.com/redirect_claims?&state=abc
&authorization_state=claims_submitted

该流程与「OAuth 之舞」介绍的常规 OAuth 授权端点一样,使用客户端与授权服务器之间的前端通道通信。不过,这里使用的重定向 URI 与授权码模式或隐式模式所用的重定向 URI 不同。无论采用哪种方式来提交声明(claims),授权服务器都会将这些声明与该票据(ticket)关联起来。客户端仍然需要再次提交该票据,才能获取令牌(token)。

  • 13.客户端再次提交该票据并尝试获取令牌。这一次成功了,因为该票据此时已经绑定了一组满足资源集策略要求的声明(claims)。这些策略还会映射到一部分 scope,使授权服务器能够确定最终令牌的实际访问权限。随后,授权服务器以 JSON 文档的形式将令牌返回给客户端,类似于 OAuth 令牌端点的返回结果。
1
2
3
4
5
6
HTTP/1.1 200 OK
Content-Type: application/json

{
  "rpt": "sbjsbhs(/SSJHBSUSSJHVhjsgvhsgvshgsv"
}

最后,客户端已经拿到了访问令牌,就可以再次尝试获取该资源。和 OAuth 一样,令牌本身的内容与格式对客户端来说是完全不透明的。

  • 14.客户端将访问令牌提交给资源服务器。客户端再次调用受保护资源,但这一次会携带刚从授权服务器获取的令牌。
1
2
3
GET /album/photo.jpg HTTP/1.1
Host: photoz.example.com
Authorization: Bearer sbjsbhs(/SSJHBSUSSJHVhjsgvhsgvshgsv

这个请求完全是标准的 OAuth Bearer Token 请求,与 UMA 没有任何特定关联。客户端为了走到这一步确实需要做一些特殊处理,但到了现在,它就可以像其他任何 OAuth 客户端一样正常工作。

  • 15.受保护资源决定了令牌能做什么。既然受保护资源已经从客户端拿到了令牌,资源服务器就需要判断:客户端出示的这个令牌,是否足以支撑它当前想要执行的操作。不过,按照 UMA 协议的设计,资源服务器与授权服务器是分离的,这使得任何依赖本地查询来获取令牌信息的做法都不可靠。好在我们在「OAuth 令牌」已经介绍了将受保护资源连接到授权服务器的两种最常见方式:JSON Web Token(JWT)和令牌内省(token introspection)。由于 UMA 是基于网络的协议,并且授权服务器在运行时通常需要保持在线以响应网络请求,因此在这一步更常用的是令牌内省,我们这里就重点看它。资源服务器发出的请求与我们之前分析过的内省请求完全一致,唯一的区别是:它不再使用自己的客户端凭据来进行调用授权,而是使用 PAT 来授权该调用。服务器返回的响应也略有不同:UMA 在内省响应的数据结构上扩展了一个 permissions 对象,其中包含更详细的信息,用于说明为签发该令牌而满足了哪些权限。
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store

   {
    "active": true,
    "exp": 1256953732,
    "iat": 1256912345,
    "permissions": [
      {
        "resource_set_id": "112210f47de98100",
        "scopes": [
          "http://photoz.example.com/dev/actions/view",
          "http://photoz.example.com/dev/actions/all"
         ],
        "exp" : 1256953732
      }
    ]
   }

该令牌本身可能同时适用于多个资源集合和多个权限集合,具体取决于授权服务器上策略引擎的配置。资源服务器需要判断令牌是否携带了满足当前请求所需的正确权限。与 OAuth 一样,令牌是否“足够好”完全由资源服务器决定。如果令牌不具备正确的权限,资源服务器可以再次执行注册权限票据并将其返回给客户端的流程,让客户端重新走一遍授权流程。客户端在收到此类错误响应后,会像之前那样重新获取一个新令牌。

  • 16.最后,资源会返回给客户端。与 OAuth 类似,一旦受保护的资源确认传入的令牌满足访问要求,就会按照其所保护的 API 规范返回相应的响应。
HTTP/1.1 200 OK
Content-Type: application/json

{
  "f_number": "f/5.6",
  "exposure": "1/320",
  "focal_length_mm": 150,
  "iso": 400,
  "flash": false
}

该响应可以是任何 HTTP 响应,也可以包含另一个 WWW-Authenticate: UMA 头,用于表明如果客户端希望获得更多访问权限,可以尝试进一步申请。在整个流程中,资源所有者的凭据和请求方的凭据都不会暴露给资源服务器或客户端。此外,双方之间也不会相互披露任何敏感的个人信息。请求方只需按资源所有者设定的策略要求,证明满足最低限度的条件即可。

健康关系信任(HEART)

正如我们在本书中反复看到的,也正如你在现实世界中一定见过的那样,OAuth 可以用于保护各种各样的协议和系统。然而,它高度的灵活性与大量可选项,也使得在不同部署之间保证互操作性与兼容性变得困难。正如「OAuth 之舞」所讨论的,在面对不同的 API 和服务提供方时,这通常不是问题。但如果你在某个单一行业内工作,例如医疗健康领域,那么你很可能会围绕一组通用 API 展开协作。在这种情况下,限定一套可靠的选项,并为部署提供清晰的指导,会更有价值。这样一来,一个厂商的客户端就能开箱即用地对接另一个厂商的授权服务器,以及第三个厂商的受保护资源。

OpenID Foundation 的 Health Relationship Trust(HEART)6 工作组成立于 2015 年7 ,旨在满足电子健康系统社区的特定需求。该工作组的目标是:为现有技术标准提供适用于医疗健康用例的配置规范(profile),并在可能的情况下,尽量保持与广泛可用、通用性更强的标准兼容。HEART 工作组建立在 OAuth、OpenID Connect 和 UMA 之上。通过收敛可选特性并将最佳实践规范化,HEART 希望同时提升安全性,并增强各个独立实现之间的互操作性。

为什么 HEART 与你息息相关

HEART 配置文件是首批标准之一,旨在提升特定行业(这里是医疗健康领域)内部的安全性与互操作性。随着越来越多行业转向 API 优先(API-first)的生态,这类“配置化(profiling)”标准很可能会愈发普遍。将来,你可能不仅要能正确实现 OAuth,还可能被要求确保某个系统或组件“符合 HEART(HEART-compliant)”。

与过去许多医疗数字化尝试不同,HEART 明确把决策能力与控制权交到终端用户手中,尤其是患者及其医疗服务提供者手里。HEART 不再将数据、控制与安全决策集中化,而是构想并打造了一个生态:数据可以在数据生产者与消费者的意愿驱动下,被安全地分布、互联。患者应当能够自带应用并连接到自己的健康记录——无论其医疗服务提供者与应用开发者此前是否认识、是否听说过对方。健康数据极其私密且敏感,因此安全性至关重要。

为实现这一目标,HEART 定义了一组技术配置文件,用于抬高 OAuth 生态中各组件之间安全性与互操作性的“最低基线”。这些配置文件由具体用例与需求驱动,对阅读本书的 OAuth 实践者而言也具有很强的借鉴意义。如果你在医疗 IT 团队工作,更应对此格外关注。

HEART 规范

HEART 生态由一套规范共同定义,每份规范覆盖技术栈中的不同部分。这些规范是其所“配置”的基础协议的一个一致性子集(conformant subset):它们不会允许或要求底层协议中不合法的行为;但在很多情况下,它们会把过去可选的组件变为必选,或利用现有的扩展点以兼容的方式加入新的可选能力。换句话说,一个符合 HEART 的 OAuth 客户端也必然是完全符合 OAuth 标准的客户端;但一个普通的 OAuth 客户端未必支持 HEART 所要求的全部特性与选项。

当然,本书的许多读者很可能从未调用或部署过任何与医疗相关的 API。于是你也许会想:“那又怎样?”这里有两点值得带走。第一,HEART 将标准 API 与安全技术以一种可以直接协同使用的方式打包在一起,使其无论基于何种实现来源都能立刻配合落地——这一模式不论在哪个行业都值得观察。第二,HEART 中做出的许多配置化决策,在医疗领域之外同样有价值。

为此,HEART 以两个清晰的维度构建:机制(mechanics)与语义(semantics)。这样的划分旨在让 HEART 在保持对医疗行业直接适用的同时,也能将影响力延伸到更广泛的领域。接下来的章节将分别介绍这两个维度及其相关规范。

HEART 机械配置文件

这三项机械规范构建在 OAuth、OpenID Connect 和 UMA 之上。这三种配置文件并不针对任何特定类型的 API,也完全不局限于医疗健康领域。因此,在需要更高安全性与互操作性的各种场景中,它们都可以派上用场。机械配置文件之间也会相互叠加,和它们所“刻画”的协议彼此叠加的方式非常相似:OpenID Connect 配置文件直接继承自 OAuth 配置文件,而 UMA 配置文件则同时继承自 OAuth 配置文件与 OpenID Connect 配置文件。

HEART 的 OAuth 配置文件与核心 OAuth 在若干方面有所不同。首先,由于该配置文件不需要像 OAuth 本身那样覆盖极其广泛的用例,因此它可以明确指引:不同类型的客户端应使用哪些 OAuth 授权类型。例如,只有浏览器内客户端才允许使用隐式授权(implicit grant),而只有处理批量操作的后端通道(back-channel)服务器应用才允许使用客户端凭证授权(client credentials grant)。HEART 还显著地从所有客户端中移除了 client secret,转而要求所有客户端不论采用何种授权类型,都必须向授权服务器注册一个公钥。该密钥用于那些需要向 Token Endpoint 进行客户端认证的客户端(例如使用授权码授权或客户端凭证授权的客户端),同时也可供其他协议用途使用。这些决策以各方略微增加的复杂度为代价,整体提升了生态系统的基础安全性。不过,密钥及其用法并非 HEART 独有:其密钥格式采用 JOSE 的 JWK(「OAuth 令牌」讨论过),而基于 JWT 的认证由 OpenID Connect 定义(我们在「使用 OAuth 2.0 进行用户认证」曾简要提到)。

HEART 的 OAuth 授权服务器还必须支持 Token Introspection 与 Token Revocation(两者均在「OAuth 令牌」介绍过),并提供标准化的服务发现(Service Discovery)端点(基于你在「使用 OAuth 2.0 进行用户认证」看到的机制)。由 HEART OAuth 授权服务器签发的所有 Token 都必须是非对称签名的 JWT(「OAuth 令牌」介绍过),并遵循该配置文件规定的必选声明(claims)与生命周期(lifetime)。HEART 配置文件还要求授权服务器提供动态客户端注册(dynamic client registration,「动态客户端注册」介绍过),当然客户端仍然可以手工注册或使用软件声明(software statements)。这一能力组合使客户端与受保护资源能够依赖一组在广泛实现中都可用的核心功能,从而实现真正的开箱即用互操作性。

HEART 的 OAuth 客户端必须始终使用 state 参数,并确保其具有最低熵要求,这将立即封堵「常见客户端漏洞」讨论过的一系列会话固定(session fixation)攻击。客户端还必须注册完整的重定向 URI(redirect URI),授权服务器需要按「常见的授权服务器漏洞」所述采用精确字符串匹配(exact string matching)进行比对。这些要求将最佳实践制度化,以提升安全性,并为开发者提供一套可依赖的能力边界。

HEART 的 OpenID Connect 配置文件明确继承了 OAuth 配置文件的全部要求与特性,从而让在 OAuth 之上实现 OIDC 的增量工作尽可能小。此外,HEART OIDC 配置文件要求身份提供方(IdP)必须始终对 ID Token 进行非对称签名,并且除了 OIDC 默认返回的未签名 JSON 之外,还要以非对称签名的 JWT 形式提供 UserInfo 端点的输出。由于所有客户端都必须注册各自的密钥,IdP 还必须为这些 JWT 提供可选加密。IdP 还必须能够处理使用 OIDC Request Object 的请求,并使用客户端密钥验证该请求。

HEART 的 UMA 配置文件在继承另外两种 HEART 机械配置文件的基础上,从 UMA 的可扩展点中选取了特定组件。例如,所有 RPT 与 PAT 都继承了 HEART 对 OAuth 访问令牌的要求,因此它们必须是带非对称签名的 JWT,并且同样支持 Token Introspection。授权服务器必须支持通过交互式 OpenID Connect 登录来收集请求方(requesting party)的声明(claims),而该登录流程本身也必须符合 HEART OIDC 配置文件。HEART 配置文件还强制要求:除了动态客户端注册之外,授权服务器还必须提供动态资源注册(dynamic resource registration)。

HEART 语义配置文件

这两个语义配置文件专为医疗健康领域设计,重点围绕 Fast Healthcare Interoperable Resources(FHIR)8 规范的使用。FHIR 定义了一个用于共享医疗数据的 RESTful API,而 HEART 的语义配置文件旨在以可预测的方式为其提供安全保障。

OAuth for FHIR 的 HEART 配置文件定义了一套标准的 scope,用于对 FHIR 资源提供差异化访问控制。HEART 配置文件按资源类型和通用访问目标对访问 scope 进行划分,使受保护资源能够以可预测的方式确定访问令牌所对应的权限,并将 HEART 的 scope 值与病历信息实现清晰映射。

UMA for FHIR 的 HEART 配置文件定义了一套标准的声明(claim)与权限 scope,可在不同类型的 FHIR 资源之间通用。这些 scope 面向单个资源,可为策略引擎提供如何执行与落实这些规则的指导。HEART 还更细致地定义了面向用户、组织和软件的特定声明,以及如何使用这些声明来请求并授予对受保护资源的访问权限。

国际政府保障(iGov)

HEART 为医疗行业的安全协议提供配置文件;同样,OpenID Foundation 的国际政府保障(iGov)工作组9也致力于定义一套用于政府系统的配置文件。iGov 的重点主要在于,如何利用 OpenID Connect 等联合身份系统,使公民与雇员能够与政府系统进行交互。

为什么 iGov 与你息息相关

iGov 规范将基于 OpenID Connect 构建,而 OpenID Connect 当然是建立在 OAuth 之上的。这些规范中提出的要求,会影响大量政府系统,以及通过这些标准协议与之对接的各类系统。传统上,政府在采用新技术方面往往行动缓慢,通常落后于行业;政府是大型组织,不愿轻易变更,并且对风险格外谨慎。这使得他们的系统具有很强的“粘性”:一旦某项做法在政府系统中落地,往往会长期存在。甚至在多年之后,当 OAuth 2.0 对互联网大多数人来说早已成为遥远的记忆,我们也早就忘了当年为什么 JSON 和 REST 会被炒得那么热时,政府系统仍很可能把这些作为遗留协议继续使用,并要求相应的接口支持与维护。

政府还有一个特点:规模足够大、影响足够强,以至于他们最终选择的方案,往往会给许多看似无关的领域带来连带约束。很多时候,政府会利用这种主导地位和影响力直接“定标准”,让其他人被动适配。迄今为止,iGov 的思路有所不同:参与其中的政府相关方并不试图定义要使用哪种技术,而是聚焦于这些技术在其具体场景下应当如何使用。这意味着,敏锐的 OAuth 开发者可以关注这项工作,从中了解一组特殊的约束条件,以及应对这些约束的方式。

最后,与 HEART 类似,iGov 的核心组件也计划在政府领域之外具备普适性。事实上,iGov 工作组正以 HEART 针对 OAuth 和 OpenID Connect 的机制性规范作为起点。因此,非政府系统未来也可能把 iGov 合规与 HEART 合规作为产品特性提供,因为这能让它们既能与这些受约束的 profile 互通,也能无缝融入更广泛的通用 OAuth 生态。将来你可能会在自己的系统中遇到这类要求,或遇到一些基于这项工作演化而来的类似规范。

iGov 的未来

截至本书出版时,iGov 工作组才刚刚起步,但已经吸引了来自全球多个主要政府的关键利益相关方参与。iGov 仍有许多未知之处,包括它是否能够成功落地并被广泛采用。不过,出于以下列出的原因,它将是一个值得持续关注的重要领域;阅读本书的 OAuth 从业者也能从这一探索中获得不少启发。如果你在政府与公民身份(Citizen Identity)相关领域工作,亲自参与其中很可能也是个不错的选择。

总结

OAuth 是构建新协议的绝佳基础。

  • UMA 让资源服务器与授权服务器能够以一种高度动态、由用户驱动的方式在不同安全域之间协同引入。
  • UMA 为 OAuth 的交互流程引入了新的参与方,尤其是请求方(Requesting Party),从而支持真正意义上的用户间共享与委托授权。
  • HEART 将多个基于 OAuth 的开放标准应用到医疗健康领域,并通过“剖析/约束”(profiling)来提升安全性与互操作性。
  • HEART 同时定义了机制层(mechanical)与语义层(semantic)的配置规范,使得经验教训不仅局限于医疗领域,还能推广到更广泛的场景。
  • iGov 仍处于开发成形的早期阶段,但它将为政府身份系统定义一套配置规范,可能带来深远影响。

我们已经能用 OAuth 和简单的 Bearer Token 做很多事情了,但如果还有另一种选择呢?下一章我们将快速了解“持有者证明”(Proof of Possession)令牌这一持续推进中的工作现状。


  1. https://docs.kantarainitiative.org/uma/rec-uma-core-v1_0.html 

  2. 不要把它和「使用 OAuth 2.0 进行用户认证」中的依赖方(RP)混淆。RqP 通常指的是人,而 RP 通常指的是计算机。是的,我们知道这有点让人困惑。 

  3. 如果你喜欢这个想法,请联系我们的出版方,并告诉他们你的看法。 

  4. 是的,这确实是在真实实现中出现过的一个真 bug。不要问,我也不想聊这个。 

  5. 这意味着,在这段“疯狂旅程”中,我们的 UMA 授权服务器正在以多种身份运作:既充当 OAuth 授权服务器,也作为受保护资源,同时还扮演客户端。 

  6. http://openid.net/wg/heart/ 

  7. 你们的一位作者是该工作组的创始成员之一。 

  8. https://www.hl7.org/fhir/ 

  9. 你们的作者之一也是这个团队的创始成员之一。世界真小,对吧?