OAuth 2.0 和 OpenID Connect
参考资料:
- OAuth 2.0 Debugger
- OpenID Connect Debugger
- Youtube Video - OAuth 2.0 and OpenID Connect
- What the Heck is OAuth
本文索引:
- 前言
- OAuth、OIDC 和 IdentityServer4 三者的关系
- OAuth 角色定义
- OAuth Tokens
- Authorization Grant
- OAuth EndPoints
- 授权通信通道
- OAuth Flows
- 安全提醒
- OAuth 不是认证协议
- Auth 2.0 的伪认证
- Open ID Connect
前言
工作中经常听到关于 OAuth2 的讨论,但很难用只言片语将这一整套体系解释清楚。本文希望通过查阅资料和开发实践,阐明 OAuth2、OIDC 和 IdentityServer4 三者关于 what、why、和 how 的问题。
OAuth、OIDC 和 IdentityServer4 三者的关系
假设有一个这样的需求: 客户端程序需要访问资源服务器的 API,又不希望每次访问都带上用户名密码做一次认证。面对这样的需求,不同的开发人员有自己的考虑,可能的实现多种多样。在经过长期沉淀和提炼之后,先驱们总结出了在不同场景下「委托授权」的最佳实践,然后为这一套最佳实践制定出一套规范 - OAuth。
OAuth 标准化了「委托授权」涉及的各个参与方、数据和 EndPoint,其目的在于为实现者提供一套统一的参考标准。由于时代原因,OAuth 1.0 已经不适合现代 Web 技术的使用场景,所以本文中谈到的 OAuth 均指 OAuth 2.0。OAuth 先于 OIDC 诞生,两者均为规范(Specifications),OAuth 对应 RFC6749,OIDC 对应 OpenID Connect Core 1.0 incorporating errata set 1,而 IdentityServer4 为 OIDC 在 .NET 平台 官方授权的实现类库。
OAuth 角色定义
Resource Owner: 资源数据的拥有者,绝大多数情况下指用户,例如,我是Facebook帐号数据的拥有者Resource Server: 受保护资源的服务方,通常以 API 形式向外界暴露资源数据,例如,Facebook的User Profile服务Client: 想要访问Resource Owner托管在Resource Server数据的第三方应用程序Public Clients: 暴露在公开的分发渠道中的应用程序,诸如Web App、Mobile App和IoT设备Confidential Clients: 受信任的机密应用程序,自存储secret。它们没有公开暴露在互联网中,无法轻易被反向工程
Authorization Server: 授权服务方,管理Resource Owner身份信息和授权列表的服务
一个简单的 OAuth 流程如下所示:
OAuth Tokens
OAuth 规范定义了两种 Token: Aceess Token 和 Refresh Token。
Access Token
Access Token 是为 Public Client 用于短期(数小时或数分钟)访问受保护资源(Resource Server)的凭证。由于其相对较短的生存期,可以无需为它们设计撤销方案,等待其自动过期即可。
Refresh Tokens
当 Resource Owner 撤销某 Client 应用程序的访问权限时,实际是撤销了其 Refresh Token,这意味着当用户再次授权 Client 访问权限时,Client 将使用新的 Refresh Token,是一种强制过期措施。而每一次通过 Refresh Token 获得的都是一枚新的 Access Token。
OAuth规范中没有对Token定义具体内容,它可以是任何格式。由于 JWT 的广泛使用,很容易将这里的Token与 JWT 对等起来,在后文的介绍中将区分它们。
Authorization Grant
Authorization Grant 是「授权许可」的泛化名称,表示 Authorization Server 在授权用户之后返回给 Client 的许可载体,Client 可借由该载体进一步向 Authorization Server 换取 Access Token。该载体通常以 code 表示,为一段随机生成的字符串,有效期非常短。
OAuth EndPoints
OAuth 规范为 Authorization Server 定义了标准化的 Endpoint 用于不同的场景:
/oauth2/authorize:Authorize Endpoint,认证用户并征求用户授权,返回Authorization Grant/oauth2/token:Token Endpoint,分发Refresh Token和Access Token,当Access Token过期后,可利用Refresh Token申请新的Access Token/oauth2/introspect: 用于检视某个Token是否仍然有效/oauth2/revoke: 用于撤销Refresh Token
关于
OAuth一个显著的争论是,开发人员不得不管理Refresh Token。这也是开发人员偏爱API Keys的原因,虽然API Keys方便很多,但任何客户端都必须保存API Keys,这加大了安全风险。
授权通信通道
OAuth 中将「委托授权」在各个流程中涉及的步骤分成了前后两步: Front Channel 和 Back Channel。
Front Channel
Front Channel 表示发生在开放的互联网中的部分,典型的场景是由「用户代理程序」发起的部分。以浏览器为例,其流程如下:
Resource Owner委托Client访问Resource ServerClient被重定向至Authorization Server的Authorize EndpointAuthorization Server认证Resource Owner,展示对话框征求用户授权Resource Owner认证通过,并同意授权后,Authorization Server返回Authorization Grant并通过浏览器重定向至Client指定的redirect_uri。
首先来看 Request:
1 | GET https://accounts.google.com/o/oauth2/auth?scope=gmail.insert gmail.send |
请求的查询字符串参数中:
scope: 表示欲访问资源的预定义资源集,此处为 Gmail 的 APIredirect_uri: 指示Authorization Server在完成授权后应该重定向的地址,该值必须与Client注册时提供的地址一致response_type: 指示请求的OAuth Flowclient_id: 标识Client,同样须与Client注册时的值一致state: 用于减轻跨站请求伪造的验证数据
接下来是 Response:
1 | HTTP/1.1 302 Found |
code: 代表Authorization Grant的字面值state: 将request中的state原封不动的传回,以供Client应用程序验证请求来源
Back Channel
Front Channel 的工作完成之后。Back Channel 开始,Client 接收到由 Authorization Server 的重定向请求后,取得 code,接着使用 code、ClientId 及 Client Credential 向 Authorization Server 请求换取 Access Token 及 Refresh Token(可选)。Client 再以 Access Token 访问受保护的资源。
Request 的源数据为:
1 | POST /oauth2/v3/token HTTP/1.1 |
grant_type: 授权许可的类型,代表了一种OAuth Flow,此案例中为authorization_code,这是典型的OAuth Flow
Response:
1 | { |
Response 的 Body 部分为 JSON 格式,描述了 Token 的信息。
值得注意的是,
access_token和refresh_token不一定是 Jwt。
OAuth Flows
Implicit Flow
Implicit Flow 又称为简化流程,因为没有任何后台服务参与使用 Authorization Grant 换取 Access Token 的流程,整个过程由 Browser 直接与 Authorization Server 通信。
Implicit Flow 常见于 SPA 应用程序,Access Token 由 Authorization Server 直接返回给浏览器,并且不支持 Refresh Token,它假定 Resource Owner 和 Public Client 运行在同一设备上。由于所有流程发生在浏览器,它是最脆弱的一种流程。
Authorization Code Flow
简称为 Code Flow,也是 OAuth 推崇的方案,该 Flow 同时采用了 Front Channel 和 Back Channel。它常见于 Web App 的场景。Client 应用程序通过 Front Channel 向 Authorization Server 申请 Authorization Code,再通过 Back Channel 用 Authorization Code 换取 Access Token。它假定 Resource Owner 和 Client 应用程序运行在不同的设备上,Access Token 始终不会传输到「用户代理应用程序」。
Client Credential Flow
Client Credential Flow 适用于 server-to-server 的场景。在这种场景中,Client 须是 Confidential Client,利用 Client Credential 作为 Client 的凭证完成授权流程,整个过程全部发生在 Back Channel,可使用对称或非对称加密算法对 Client Credential 进行加密。
Resource Owner Password Flow
这是一种过时的流程,已不再推荐使用。这种 Flow 将用户的帐号密码通过「用户代理应用程序」向 Authorization Server 请求 Access Token。通常情况下它不支持 Refresh Token 并假定 Resource Owner 和 Public Client 运行在同一设备。
Device Flow
用于类似 TV 等硬件设备,或仅仅运行一个 Cli 的程序,直接与 Authorization Server 通信取得一个 code,再用 code 换取 Access Token 的流程。
安全提醒
上述所有这些 Flow 都不同程度地涉及了 OAuth 规范中定义的角色,应该采用哪种 Flow 取决于 Client 的类型,但有以下几点值得注意:
- 使用
state参数验证完整性以减轻 CSRF(Cross-Site Request Forgery) 攻击 - 启用白名单验证用于重定向的
Url - 使用
ClientId绑定特定的Client与指定的Grant Type和Token类型 - 确保
Confidential Clients的Secret不会泄露
OAuth 规范的 Token 并不会与终端用户绑定,它可以像 Session Cookie 一样被传递,任何人都能将其拷贝并用在 Authoriazaion Header 中。OAuth 将授权策略与身份认证解耦,它可以实现细粒度与粗粒度授权的融合,并且代替传统 Web App 的访问管理机制。同时,OAuth 对访问受保护资源 API 提供了限制和撤销机制,确保特定 Client 只能可访问受限的 API。
OAuth 不是认证协议
OAuth 是一套授权流程的框架并非协议。它关注的重点是如何委托 Authorization Server 进行授权,而不是认证。OAuth 的官方文档没有提到有关用户的操作,所有 Flow 的目的都是为了取得一个可以访问受保护资源 API 的 Access Token。
Auth 2.0 的伪认证
Facebook Connect 和 Twitter 最早使用 Access Token 向一个 /me 的 Endpoint 取得用户数据,该 Endpoint 并没有在 OAuth 的规范中定义,这种方式被称为伪认证。
Open ID Connect
为了解决伪认证以非规范化的方式被滥用,OAuth 框架的一部分、Facebook Connect 以及 SAML2.0 被合并为 OpenID Connect(OIDC),OIDC 在 OAuth 2.0 基础上扩展了一个经过签名的 ID Token,以及一个 UserInfo Endpoint 用于获取用户数据。OIDC 还标准化了针对身份信息的 scope 和 claim,例如 profile、email、address 和 phone。除此之外,OIDC 将注册、发现等功能作为组件纳入规范之中。与 OAuth 的 Request 相比,其 scope 可接受新的值,如 openid 和 email:
1 | GET https://accounts.google.com/o/oauth2/auth? |
以及在 Authorization Grant 向 Authorization Server 换取 Token 后的 Response 中新增了 ID Token:
1 | { |
ID Token 是一个 JWT(JSON Wbe Token),JWT 非常小巧,易于传输,由 3 部分组成:
header: 包含签名所使用的算法body: 包含代表用户身份信息的claimsignature: 用于完整性验证的签名信息
OIDC Flow 涉及以下步骤:
- 发现
OIDC元数据 - 发起一种
OAuth Flow来取得ID Token和Access Token - 取得用于 JWT 签名的
Key并动态注册Client应用程序(可选) Client基于 JWT 的日期与签名离线验证ID Token- 利用
Access Token按需获取用户数据