OAuth 2.0 完整文档(英文)

1. 角色:

  • 第三方应用: "Client": 客户端是一个想访问用户账号的应用,在此之前客户端必须获得用户的许可。
  • 接口: "Resource Server": 资源服务器是一个接口服务器,用于访问用户信息。
  • 授权服务器: 提供用户批准或拒绝请求的接口的服务器。在较小的实现中,这可能和 API 服务器是一个服务器,但是较大规模的部署中通常将其构建为单独的组件。
  • 用户: "Resource Ownner": 资源拥有者是一个给出自己部分账号信息供访问的人。

    2. 创建一个应用:

    在开始 OAuth 流程之前,必须先向 OAuth 服务注册你的应用。注册新的应用时,通常要注册一些基本信息,如应用的名称、网站、徽标等。此外,必须注册用于将用户重定向到 Web 服务器、单页应用或移动应用的重定向 URI。

  • Redirect URIs: OAuth 服务仅将用户重定向到注册过的 URI,这有助于防止一些攻击。任何 HTTP 重定向 URI 都必须用 TLS 安全保护,即,仅重定向到以 “https” 开头的 URI。这样可以防止在授权过程中令牌被截获。原生移动应用可以注册一个使用自定义 URL 方案的重定向 URI,它可能类似于 demoapp://redirect

  • Client ID and Secret: 注册应用后,您将收到一个客户端 ID 和一个客户端密码。客户端 ID 作为一个可以公开的信息,可以用来构建登录 URL,或者包含在 Javascript 代码中。而客户端密码必须保持机密性。如果部署的应用程序不能保持机密(例如单页 Javascript 应用或本机应用程序),就不要使用客户端密码。最好情况,OAuth 服务最初就不应向这种类型的应用发布客户端密码。

    3. 授权 Authorization:

    Oauth2 第一步就是从用户获得授权。对于基于浏览器的应用或移动应用来说,通常是向用户展示授权服务提供的界面就行了。
    Oauth2 提供几种常见的 授权类型 "grant types"

  • Authorization Code - 主要用于 web 服务器、基于浏览器和移动的应用

  • Password - 主要用于使用用户名和密码登陆
  • Client credentials - 主要用于应用程序访问
  • Implicit - 之前被推荐用在没机密性的客户端上,目前已经被 Authorization Code 取代(不使用客户端密码)!
    接下来会详细描述每种用例。

    3.1 Web 服务器应用

    Web 服务器应用是处理 Oauth 服务时最常见的应用类型。Web 服务器应用使用服务器端语言编写,在源代码对外不可见的服务器上运行。这就意味着 Web 服务器应用在和授权服务器通信时可以使用客户端密码,这样就可以避免一些攻击侵入。

    授权环节:

    创建一个“登录”链接,链接可以引导用户到:

    1. 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 - 由你的应用生成的一个随机字符串,稍后你将拿它作校验(防范CSRF攻击)

用户将看到授权提示:
OAuth 2.0 简明教程 - 图1
oauth-authorization-prompt
如果用户选择“允许”,授权服务器返回授权码,把用户重定向回你的网站。

  1. https://example-app.com/cb?code=AUTH_CODE_HERE&state=1234zyx
  • code - 授权服务器返回的授权码(在查询字符串中)
  • state - 授权服务器返回的状态值(和你传过去的状态值相同)

你首先应该比较返回的状态值是否和刚开始的一致(通常你可以把状态值存储在 cookie 或 session 中)。这样能确保重定向端点没有安全问题。

交换令牌环节:

你的应用会拿授权码交换访问令牌:

  1. POST https://api.authorization-server.com/token
  2. grant_type=authorization_code&
  3. code=AUTH_CODE_HERE&
  4. redirect_uri=REDIRECT_URI&
  5. client_id=CLIENT_ID&
  6. client_secret=CLIENT_SECRET
  • grant_type=authorization_code - 本次的授权类型是: authorization_code
  • code=AUTH_CODE_HERE -这是你从查询字符串收到的 code
  • redirect_uri=REDIRECT_URI - 必须和原始链接中提供的重定向 URI 相同(即:和授权环节的重定向 URL 相同)
  • client_id=CLIENT_ID - 你首次创建应用程序时收到的客户端ID
  • client_secret=CLIENT_SECRET - 本次请求是由 Web 服务器类型的应用发出的,因此要有客户端密码(单页应用/基于浏览器的应用不需要!!!

授权服务器回复访问令牌和过期时间:

  1. {
  2. "access_token":"RsT5OjbzRn430zqMLgV3Ia",
  3. "expires_in":3600
  4. }

或者一个错误:

  1. {
  2. "error":"invalid_request"
  3. }

请注意,授权服务必须要求应用程序提前注册它们的重定向 URI!

3.2 单页应用

单页应用/基于浏览器的应用,从网页加载源代码后完全在浏览器中运行。因为源代码对浏览器完全可见,无法保证客户端密码的机密性。所以不能使用客户端密码。除此之外,和上面例子的流程完全一样。
注意: 在以前,单页应用/基于浏览器的应用建议使用 "Implicit"授权类型的流程(该流程会立即返回访问令牌,不需要令牌交换环节)。不过现在业界最佳实践已经改变了,当前的建议是在不使用客户端密码的前提下使用 “授权码 authorization code” 授权类型流程。该流程提供更多安全方案的可能,比如:PKCE 扩展(参考:OAuth 2.0安全最佳当前实践OAuth 2.0用于基于浏览器的应用程序

3.3 移动应用

像基于浏览器的应用一样,移动应用也不能保证客户端密码的机密性。因为这个,移动应用在使用 Oauth 流程时也不能用客户端密码。为确保 Oauth 流程的安全性,移动应用必须牢记一些额外的事项!
注意: 在以前,移动应用/原生应用建议使用 "Implicit"授权类型的流程,不过现在业界最佳实践已经改变了,当前的建议是在不使用客户端密码的前提下使用 “授权码 authorization code” 授权类型流程。当然这里还为移动应用准备了一些有价值的附加建议。

授权环节:

创建一个 “登录” 按钮, 该按钮将引导用户到提供授权服务的原生应用,或者提供授权服务的移动网页(都在手机上)。如果你用的是 iphone, 应用可以注册自定义 uri 方案, 如 “facebook://“, 每当访问具有该协议的 url 时, 就会启动本机 facebook 应用程序。如果你用的是 android, 应用可以注册 url 匹配模式, 如果访问了与该模式匹配的 url, 该模式将启动提供授权服务的原生应用。
使用(提供授权服务的)原生应用:
如果用户已经安装了 Facebook 移动应用,按钮将引导用户定向到以下URL:(参数不再做解释)

  1. fbauth2://authorize?response_type=code&client_id=CLIENT_ID
  2. &redirect_uri=REDIRECT_URI&scope=email&state=1234zyx

如果授权服务支持 PKCE 扩展(推荐),你也应该包含以下参数。提前是你的应用(第三方应用)中创建的有一个“验证码 code_verifier”(在你的应用本地存储的一个随机字符串,交换令牌环节会用到)

  • code_challenge=XXXXXXX - 验证码(code_verifier)经过混淆后的字符串( base64 编码后 sha256 散列)
  • code_challenge_method=S256 - 指明混淆的加密方法,本例中为 sha256

注意: 重定向的 URI 可能看起来像 fb00000000://author...,其中的协议是应用在 OS 中注册的自定义 URL 方案。
在 Web 浏览器中使用授权服务:
如果手机上没有提供授权服务的移动/原生应用,可以启动移动浏览器使用标准的 Web 授权 URL。请注意,绝不应该在你自己的应用中使用嵌入式的 web 视图,因为这样不能保证用户正在真正提供授权服务的网站上输入密码而不是钓鱼站点中。
你应该启动一个移动浏览器,也可以在你的应用中使用 新的 iOS 的 “SafariViewController” 接口来启动一个内置浏览器。这个接口是 iOS 9 中新增的接口,它提供了一种在应用程序内部启动浏览器的机制,该浏览器还可以显示地址栏,以便用户可以确认他们在正确的网站上,又和真正的 Safar i浏览器共享 cookie。它还保护浏览器的内容不被监听和修改,因此可以认为是安全的。
同样,如果授权服务支持 PKCE,那么应该像上面描述的那样包括那些参数。(参数不再做解释)

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

交换令牌环节:

当单击“批准”之后,用户将被重定向回应用程序,其URL就像这样: fb00000000://authorize?code=AUTHORIZATION_CODE&state=1234zyx
你的移动应用应该首先验证该状态值是否和初始请求中使用的状态值相同,然后就可以拿授权代码交换访问令牌。
交换令牌的过程看起来与 Web 服务器应用下的完全相同,只是没有发送客户端密码。如果服务器支持 PKCE,则需要包括如下所述的附加参数:

  1. POST https://api.authorization-server.com/token
  2. grant_type=authorization_code&
  3. code=AUTH_CODE_HERE&
  4. redirect_uri=REDIRECT_URI&
  5. client_id=CLIENT_ID&
  6. code_verifier=VERIFIER_STRING
  • code_verifier=VERIFIER_STRING - 验证码:明文字符串(你之前把它哈希散列混淆后用来创建 code_challenge)

授权服务器将验证此请求并返回访问令牌。如果服务器支持PKCE,则授权服务器将校验该验证码(对提供的明文进行哈希散列,并确认散列混淆后和初始授权请求中发送的散列字符串是否一致)。这就确保了在不支持客户端密码的第三方客户端中使用授权码授权类型的流程安全性。

3.4 其他授权类型

Password:
OAuth 2还提供了一种 “Password” 授权类型, 该类型用来直接使用用户名和密码交换访问令牌。 很显然,应用程序需要搜集用户的密码,所以该授权类型只能提供给被授权服务自己创建的应用使用。 比如,一个 Twitter 的应用程序可以使用这种授权类型登录 Twitter 的移动或桌面应用。
要使用密码授权类型,只需发出如下 POST 请求:

  1. POST https://api.authorization-server.com/token
  2. grant_type=password&
  3. username=USERNAME&
  4. password=PASSWORD&
  5. client_id=CLIENT_ID

授权服务器回复一个访问令牌,令牌的格式和其他授权类型的相同。 注意:不能包含客户端密码!因为该授权类型假定使用情景都是移动或桌面应用,这样是无法保护密码的。
Application access:
有时,应用可能需要访问令牌来代表自己,而不是代表用户进行操作。比如,一个服务可能提供一种方式让应用更新自己信息(如:网址,徽标,或者他们可能希望获取应用上的用户的统计数据)。这种情况下,应用除了需要特殊用户的上下文外,也需要为自己获取访问令牌, Oauth 为此提供了 “客户端凭证 client_credentials” 授权类型。
要使用客户端凭证授权类型,请发出如下POST请求:

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

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

4. 发起认证请求

无论哪种授权类型,最终结果是获得访问令牌。现在有了访问令牌,就可以向API发出请求了。您可以使用 cURL 快速做出 API 请求,如下所示:

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

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

5. OAuth 2.0 和 OAuth 1.0 的不同之处

OAuth 1.0 主要基于当时现有的专属协议,比如 Flickr 的 “FlickrAuth” 和 Google 的 “AuthSub”,它是当时实际运作经验提炼的最佳解决方案。尽管如此,在该协议下运作了几年后,社区通过不断学习,觉得 Oauth 1.0 有三个的方面需要反思和提升:

5.1 认证和签名

大部分开发人员对 OAuth 1.0 的烦恼来自使用客户端 ID 和密码对请求进行签名并加密的需求。 无法轻松复制和粘贴 cURL 样例,快速上手变得很难。
OAuth 2 认识到了难处并将签名替换为浏览器、客户端和API之间的所有通信都需要HTTPS。

5.2 用户体验和其他可选授权流程

OAuth 包括两个主要部分,获取访问令牌,并使用访问令牌进行请求。OAuth 1.0 最适合于桌面 Web 浏览器,但是无法为本地桌面和移动应用程序或设备(如游戏或电视控制台)提供良好的用户体验。
OAuth 2.0 为原生应用提供了更好的用户体验,并且支持扩展协议以便与未来的设备兼容。

5.3 弹性伸缩下的性能

随着较大的提供商开始使用 OAuth 1.0,社区很快意识到该协议不能很好地扩展。许多步骤需要状态管理和临时凭证,这需要共享存储,并且很难跨数据中心同步。OAuth 1.0 还要求 API 服务器能够访问应用程序的ID和密码,这常常会破坏大多数大型提供商的体系结构(大多数大型提供商的授权服务器和API服务器是完全独立的)。
OAuth 2.0 支持获取用户授权和处理API调用两个角色的分离。较大的提供商需要这种弹性以便自由地实现功能,而较小的提供商如果愿意,可以为两个角色使用相同的服务器。