这篇文章以简化的格式描述了 OAuth 2.0,以帮助开发人员和服务提供商实现该协议。

OAuth 2 规范读起来可能有点混乱,所以我写了这篇文章来帮助以简化的格式描述术语。核心规范将许多决策留给实现者,通常基于实现的安全权衡。这篇文章没有描述成功实施 OAuth 2 所需的所有可能决策,而是做出适用于大多数实现的决策。

注意:本文已根据 OAuth 2.0 的当前最佳实践从原始 2012 版本更新而来。原始版本可以在这里找到。

角色

第三方应用程序:“客户端”

客户端是尝试访问用户帐户的应用程序。它需要先获得用户的许可,然后才能执行此操作。

API:“资源服务器”

资源服务器是用于访问用户信息的 API 服务器。

授权服务器

这是提供用户批准或拒绝请求的接口的服务器。在较小的实现中,这可能与 API 服务器是同一台服务器,但更大规模的部署通常会将其构建为单独的组件。

用户:“资源所有者”

资源所有者是授予访问其帐户某些部分的权限的人员。

创建应用程序

在开始 OAuth 过程之前,必须先向服务注册新应用。注册新应用时,通常会注册应用名称、网站、徽标等基本信息。此外,还必须注册一个重定向 URI,用于将用户重定向到 Web 服务器、基于浏览器的应用程序或移动应用程序。

重定向 URI

该服务只会将用户重定向到已注册的 URI,这有助于防止某些攻击。任何 HTTP 重定向 URI 都必须通过 HTTPS 提供。这有助于防止令牌在授权过程中被截获。本机应用可能会使用应用程序的自定义 URL 方案注册重定向 URI,这可能类似于 demoapp://redirect .

客户端 ID 和密码

注册应用后,你将收到一个客户端 ID 和一个(可选)的客户端密码。客户端 ID 被视为公共信息,用于构建登录 URL,或包含在页面上的 Javascript 源代码中。客户端密码必须保密。如果已部署的应用无法对机密保密,例如单页 Javascript 应用或本机应用,则不会使用该密钥,理想情况下,该服务首先不应向这些类型的应用发布机密。

授权

OAuth 2 的第一步是从用户那里获得授权。对于基于浏览器或移动的应用程序,这通常是通过向用户显示服务提供的界面来实现的。

OAuth 2 为不同的用例提供了几种“授权类型”。定义的授权类型包括:

  • Authorization Code 在 Web 服务器、基于浏览器的应用程序和移动应用程序上运行的应用程序的授权代码

  • Password 使用用户名和密码登录的密码(仅适用于第一方应用)

  • Client credentials 在没有用户在场的情况下进行应用程序访问的客户端凭据

  • Implicit 隐式以前被推荐给没有秘密的客户端,但已被使用授权代码授予与PKCE取代。

下面将详细介绍每个用例。

Web 服务器应用程序

Web 服务器应用程序是您在处理 OAuth 服务器时遇到的最常见的应用程序类型。Web 应用程序是用服务器端语言编写的,并在应用程序源代码不向公众开放的服务器上运行。这意味着应用程序在与授权服务器通信时能够使用其客户端密钥,这有助于避免许多攻击媒介。

授权

创建一个“登录”链接,将用户发送到:

https://authorization-server.com/auth?response_type=code&
  client_id=CLIENT_ID&redirect_uri=REDIRECT_URI&scope=photos&state=1234zyx
  • response_type=code 指示您的服务器希望收到授权代码
  • client_id 首次创建应用程序时收到的客户端 ID
  • redirect_uri 示授权完成后要将用户返回到的 URI
  • scope 一个或多个范围值,指示您希望访问用户帐户的哪些部分
  • state 应用程序生成的随机字符串,稍后将对其进行验证

用户看到授权提示

OAuth Authorization Prompt

如果用户单击“允许”,则该服务会使用授权代码将用户重定向回您的网站

https://example-app.com/cb?code=AUTH_CODE_HERE&state=1234zyx
  • code 服务器返回查询字符串中的授权代码
  • state 服务器返回您传递的相同状态值

应首先比较此状态值,以确保它与开始时使用的状态值匹配。您通常可以将状态值存储在 Cookie 或会话中,并在用户返回时进行比较。这有助于确保您的重定向终结点不会被诱骗尝试交换任意授权代码。

获取访问令牌

您的服务器通过向授权服务器的令牌终结点发出 POST 请求来交换访问令牌的授权代码:

POST https://api.authorization-server.com/token
  grant_type=authorization_code&
  code=AUTH_CODE_HERE&
  redirect_uri=REDIRECT_URI&
  client_id=CLIENT_ID&
  client_secret=CLIENT_SECRET
  • grant_type=authorization_code 此流的授权类型为 authorization_code
  • code=AUTH_CODE_HERE 这是您在查询字符串中收到的代码
  • redirect_uri=REDIRECT_URI 必须与原始链接中提供的重定向 URI 相同
  • client_id=CLIENT_ID 首次创建应用程序时收到的客户端 ID
  • client_secret=CLIENT_SECRET 由于此请求是从服务器端代码发出的,因此包含密码

服务器使用访问令牌和过期时间进行回复

{
  "access_token":"RsT5OjbzRn430zqMLgV3Ia",
  "expires_in":3600
}

或者如果出现错误

{
  "error":"invalid_request"
}

安全性:请注意,该服务必须要求应用预注册其重定向 URI。

单页应用

从网页加载源代码后,单页应用程序(或基于浏览器的应用程序)完全在浏览器中运行。由于整个源代码可供浏览器使用,因此它们无法维护客户端密钥的机密性,因此在这种情况下不使用密钥。该流程基于上述授权代码流程,但添加了用于每个请求的动态生成的密钥。这称为PKCE扩展。

注意:以前,建议基于浏览器的应用使用“隐式”流,该流在重定向中立即返回访问令牌,并且没有令牌交换步骤。自最初编写规范以来,行业最佳实践已更改为建议使用没有客户端密码的授权代码流。这提供了创建安全流的更多机会,例如使用 PKCE 扩展。延伸阅读:OAuth 2.0 安全最佳实践 (ietf.org)、OAuth 2.0 for Browser-Based Apps (ietf.org)。

授权

创建一个长度在 43-128 个字符之间的随机字符串,然后生成该字符串的 url-safe base64 编码的 SHA256 哈希值。原始随机字符串称为 code_verifier ,哈希版本称为 code_challenge

创建一个随机字符串(代码验证器),例如 5d2309e5bb73b864f989753887fe52f79ce5270395e25862da6940d5

创建 SHA256 哈希值,然后对字符串进行 base64 编码(代码质询): MChCW5vD-3h03HMGFZYskOSTir7II_MMTb8a9rJNhnI

(您可以在 example-app.com/pkce 上使用 helper 实用程序生成密钥和哈希值。

创建一个“登录”链接,如上面的授权代码流,但现在在请求中包含代码质询:

https://authorization-server.com/auth?response_type=code&client_id=CLIENT_ID&redirect_uri=REDIRECT_URI&scope=photos&state=1234zyx&code_challenge=CODE_CHALLENGE&code_challenge_method=S256
  • response_type=code - 指示您的服务器希望收到授权代码
  • client_id - 首次创建应用程序时收到的客户端 ID
  • redirect_uri - 指示授权完成后要将用户返回到的 URI
  • scope - 一个或多个范围值,指示您希望访问用户帐户的哪些部分
  • state -应用程序生成的随机字符串,稍后将对其进行验证
  • code_challenge - 密钥的 URL 安全 base64 编码的 SHA256 哈希
  • code_challenge_method=S256 - 指示您使用的哈希方法 (S256)

用户看到授权提示

OAuth Authorization Prompt如果用户单击“允许”,则该服务会使用身份验证代码将用户重定向回您的网站

https://example-app.com/cb?code=AUTH_CODE_HERE&state=1234zyx
  • code - 服务器返回查询字符串中的授权代码
  • state - 服务器返回您传递的相同状态值

应首先比较此状态值,以确保它与开始时使用的状态值匹配。您通常可以将状态值存储在 cookie 中,并在用户返回时进行比较。这样可以确保您的重定向终结点不会被诱骗尝试交换任意授权代码。

你可以在我的博客文章 OAuth 隐式流死了吗?

获取访问令牌

现在,您需要将授权代码交换为访问令牌,但无需提供预注册的客户端密钥,而是发送在流程开始时生成的 PKCE 密钥。

POST https://api.authorization-server.com/token
  grant_type=authorization_code&
  code=AUTH_CODE_HERE&
  redirect_uri=REDIRECT_URI&
  client_id=CLIENT_ID&
  code_verifier=CODE_VERIFIER
  • grant_type=authorization_code - 此流的授权类型为 authorization_code
  • code=AUTH_CODE_HERE - 这是您在查询字符串中收到的代码
  • redirect_uri=REDIRECT_URI - 必须与原始链接中提供的重定向 URI 相同
  • client_id=CLIENT_ID - 首次创建应用程序时收到的客户端 ID
  • code_verifier=CODE_VERIFIER - 您在开始时生成的随机密钥

授权服务器将对验证程序进行哈希处理,并将其与请求中发送的质询进行比较,并且仅在它们匹配时颁发访问令牌。这确保了即使有人能够拦截授权代码,他们也无法使用它来获取访问令牌,因为他们没有机密。

移动应用

与基于浏览器的应用程序一样,移动应用程序也无法维护客户端机密的机密性。因此,移动应用程序还使用不需要客户端密钥的 PKCE 流。移动应用还应牢记一些其他问题,以确保 OAuth 流的安全性。

注意:以前,建议移动应用和原生应用使用隐式授权。自最初编写规范以来,行业最佳实践已更改为建议对本机应用使用没有机密的授权代码流。对于本机应用程序,还有一些其他建议也值得一读。

授权

创建一个“登录”按钮,将用户发送到手机上服务的本机应用程序或服务的移动网页。应用可以注册自定义 URI 方案,例如“example-app://”,以便在访问具有该协议的 URL 时启动本机应用,或者它们可以注册 URL 模式,如果访问与该模式匹配的 URL,则该模式将启动本机应用。

使用服务的本机应用程序

如果用户安装了原生 Facebook 应用,请将其定向到以下 URL:

fbauth2://authorize?response_type=code&client_id=CLIENT_ID
  &redirect_uri=REDIRECT_URI&scope=email&state=1234zyx
  • response_type=code - 表示您的服务器希望收到授权码
  • client_id=CLIENT_ID - 首次创建应用程序时收到的客户端 ID
  • redirect_uri=REDIRECT_URI - 指示授权完成后要将用户返回到的 URI,例如 fb00000000://authorize
  • scope=email - 一个或多个范围值,指示您希望访问用户帐户的哪些部分
  • state=1234zyx -应用程序生成的随机字符串,稍后将对其进行验证

对于支持 PKCE 扩展的服务器(如果要构建服务器,则应支持 PKCE 扩展),还将包含以下参数。首先,创建一个“代码验证器”,它是应用存储在本地的随机字符串。

  • code_challenge=XXXXXXX - 这是代码验证程序字符串的 sha256 哈希的 base64 编码版本
  • code_challenge_method=S256 - 指示用于计算质询的哈希方法,在本例中为 sha256。

请注意,重定向 URI 可能类似于 fb00000000://authorize 协议是应用已向操作系统注册的自定义 URL 方案。

使用 Web 浏览器

如果服务没有本机应用程序,则可以启动移动浏览器到标准 Web 授权 URL。请注意,您永远不应该在自己的应用程序中使用嵌入式 Web 视图,因为这不能保证用户实际上是在服务网站而不是网络钓鱼网站中输入密码。

您应该启动本机移动浏览器,或使用新的 iOS“SafariViewController”在应用程序中启动嵌入式浏览器。此 API 是在 iOS 9 中添加的,它提供了一种在应用程序内启动浏览器的机制,该浏览器既显示地址栏,以便用户可以确认他们在正确的网站上,又与真正的 Safari 浏览器共享 cookie。它还可以防止应用程序检查和修改浏览器的内容,因此可以认为是安全的。

https://facebook.com/dialog/oauth?response_type=code&client_id=CLIENT_ID
  &redirect_uri=REDIRECT_URI&scope=email&state=1234zyx

同样,如果服务支持 PKCE,则应包括这些参数以及上述参数。

  • response_type=code - 表示您的服务器希望收到授权码
  • client_id=CLIENT_ID - 首次创建应用程序时收到的客户端 ID
  • redirect_uri=REDIRECT_URI - 指示授权完成后要将用户返回到的 URI,例如 fb00000000://authorize
  • scope=email - 一个或多个范围值,指示您希望访问用户帐户的哪些部分
  • state=1234zyx - 应用程序生成的随机字符串,稍后将对其进行验证

用户将看到授权提示

Facebook Authorization Prompt

获取访问令牌

单击“批准”后,用户将被重定向回您的应用程序,URL 如下

fb00000000://authorize?code=AUTHORIZATION_CODE&state=1234zyx

移动应用程序应首先验证状态是否与初始请求中使用的状态相对应,然后可以将授权代码交换为访问令牌。

令牌交换看起来与交换 Web 服务器应用案例中的代码相同,只是不发送密钥。如果服务器支持 PKCE,则需要包含如下所述的附加参数。

POST https://api.authorization-server.com/token
  grant_type=authorization_code&
  code=AUTH_CODE_HERE&
  redirect_uri=REDIRECT_URI&
  client_id=CLIENT_ID&
  code_verifier=VERIFIER_STRING
  • grant_type=authorization_code - 此流的授权类型为 authorization_code
  • code=AUTH_CODE_HERE - T这是您在查询字符串中收到的代码
  • redirect_uri=REDIRECT_URI - 必须与原始链接中提供的重定向 URI 相同
  • client_id=CLIENT_ID - 首次创建应用程序时收到的客户端 ID
  • code_verifier=VERIFIER_STRING - 您之前为创建code_challenge而哈希处理的明文字符串

授权服务器将验证此请求并返回访问令牌。

如果服务器支持 PKCE,则授权服务器将识别此代码是通过代码质询生成的,并将对提供的明文进行哈希处理,并确认哈希版本与初始授权请求中发送的哈希字符串相对应。这可确保在不支持机密的客户端中使用授权代码流的安全性。

其他授权类型

密码

OAuth 2 还提供“密码”授权类型,可用于直接交换访问令牌的用户名和密码。由于这显然需要应用程序收集用户的密码,因此它只能由服务本身创建的应用程序使用。例如,本机 Twitter 应用可以使用此授权类型登录移动或桌面应用。

要使用密码授予类型,只需发出如下 POST 请求:

POST https://api.authorization-server.com/token
  grant_type=password&
  username=USERNAME&
  password=PASSWORD&
  client_id=CLIENT_ID
  • grant_type=password - 此流的授权类型为密码
  • username=USERNAME - 应用程序收集的用户用户名
  • password=PASSWORD - 应用程序收集的用户密码
  • client_id=CLIENT_ID - 首次创建应用程序时收到的客户端 ID

服务器使用与其他授权类型相同的格式的访问令牌进行回复。

请注意,此处不包括客户端密钥,前提是密码授予的大多数用例都是无法保护密钥的移动或桌面应用。

应用程序访问

在某些情况下,应用程序可能需要访问令牌来代表自己而不是用户执行操作。例如,该服务可能为应用程序提供一种更新其自身信息(例如其网站URL或图标)的方法,或者他们可能希望获取有关应用程序用户的统计信息。在这种情况下,应用程序需要一种方法,在任何特定用户的上下文之外获取其帐户的访问令牌。OAuth 为此目的提供 client_credentials 授权类型。

若要使用客户端凭据授予类型,请发出如下所示的 POST 请求:

POST https://api.authorization-server.com/token
    grant_type=client_credentials&
    client_id=CLIENT_ID&
    client_secret=CLIENT_SECRET

响应将包含与其他授权类型格式相同的访问令牌。

发出经过身份验证的请求

所有授权类型的最终结果都是获取访问令牌。

现在,您已经有了访问令牌,可以向 API 发出请求。您可以使用 cURL 快速发出 API 请求,如下所示:

curl -H "Authorization: Bearer RsT5OjbzRn430zqMLgV3Ia" \
https://api.authorization-server.com/1/me

就是这样!确保您始终通过 HTTPS 发送请求,并且永远不要忽略无效证书。HTTPS 是保护请求不被拦截或修改的唯一方法。

与 OAuth 1.0 的区别

OAuth 1.0 主要基于现有的专有协议,例如 Flickr 的“FlickrAuth”和 Google 的“AuthSub”。结果代表了基于实际实施经验的最佳解决方案。然而,经过几年的协议工作,社区学到了足够的知识,可以在三个主要领域重新思考和改进协议,在这些领域,OAuth 1.0被证明是有限的或令人困惑的。 你可以在我的书《OAuth 2.0 Simplified》中详细阅读有关此内容的更多信息。

资源

本文之前的版本: