OAuth2 快速入门

1 OAuth简述

OAuth 2.0 是一个授权协议,它允许软件应用代表(而不是充当)资源拥有者去访问资源拥有者的资源。应用向资源拥有者请求授权,然后取得令牌(token),并用它来访问资源,并且资源拥有者不用向应用提供用户名和密码等敏感数据。

2 OAuth角色

OAuth整个授权过程中定义了4种角色:

  • 客户端(Cilent):代表资源拥有者访问受保护资源的软件,它使用OAuth 来获取访问权限;
  • 资源拥有者(Resource Owner):是有权将访问权限授权给客户端的主体,在大多数情况下,资源拥有者是一个人,他使用客户端软件访问受他控制的资源;
  • 资源服务器(Resource Server):资源服务器能够通过HTTP 服务器进行访问,在访问时需要OAuth 访问令牌。受保护资源需要验证收到的令牌,并决定是否响应以及如何响应请求;
  • 授权服务器(Authorization Server):一个HTTP 服务器,它在OAuth 系统中充当中央组件。授权服务器对资源拥有者和客户端进行身份认证,让资源拥有者向客户端授权、为客户端颁发令牌。某些授权服务器还会提供额外的功能,例如令牌内省、记忆授权决策;

假设你使用了一个照片云存储服务和一个云打印服务,并且想使用云打印服务来打印存放在云存储服务上的照片。很幸运,这两个服务能够使用API 来通信。这很好,但两个服务由不同的公司提供,这意味着你在云存储服务上的账户和在云打印服务上的账户没有关联。使用OAuth 可以解决这个问题:授权云打印服务访问照片,但并不需要将存储服务上的账户密码交给它。
在这上面这一段中:
客户端 : 云打印服务
资源拥有者:你
资源服务器,授权服务器:照片云存储服务

3 OAuth授权许可类型

3.1 授权码许可类型(Grant Type: Authorization Code)

资源拥有者通过在授权服务器完成登录,获取授权码的形式,客户端通过Ajax请求最终兑换令牌,该种形式使得令牌对外是不可见的,一定程度上保证了令牌的安全,通常用于Web端的OAuth2.0 开发。
具体流程步骤为:
(1)资源拥有者在授权服务器完成登录,并完成授权,资源服务器重定向至客户端,并附带一个临时授权码,该授权码是短时效性的;
(2)客户端获取到临时授权码后,通过临时授权码以及其他应用信息,向资源服务器接口请求,以此来换取令牌(后端接口请求)。
授权码许可类型流程

3.2 隐式许可类型(Grant Type:Implicit)

有些 Web 应用是纯前端应用,没有后端。此时就没有使用授权码形式的必要了,而是直接返回令牌,省略了授权码再去兑换令牌的步骤(该步骤本是在后端进行,目的是对前端隐藏令牌内容)。隐式许可流程不可用于获取刷新令牌。因为浏览器内的应用具有短暂运行的特点,只会在被加载到浏览器的期间保持会话。

// 请求,response_type 参数的值为token,其为一个授权页面,用户需要在上面完成授权,授权完成后,会重定向至redirect_uri
HTTP/1.1 302 Moved Temporarily
Location: //localhost:9001/authorize?response_type=token&scope=foo&client_
id=oauth-client-1&redirect_uri=http%3A%2F%2Flocalhost%3A9000%2Fcallback&state
=Lwt50DDQKUB8U7jtfLQCVGDL9cnmwHH1
Vary: Accept
Content-Type: text/html; charset=utf-8
Content-Length: 444
Date: Fri, 31 Jul 2015 20:50:19 GMT
 
// 返回,直接返回accss_token,注意此处为 #access_token
GET /callback#access_token=987tghjkiu6trfghjuytrghj&token_type=Bearer
HTTP/1.1
Host: localhost:9000
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:39.0)
Gecko/20100101 Firefox/39.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Referer: //localhost:9001/authorize?response_type=code&scope=foo&client_id=
oauth-client-1&redirect_uri=http%3A%2F%2Flocalhost%3A9000%2Fcallback&state=
Lwt50DDQKUB8U7jtfLQCVGDL9cnmwHH1

3.3 客户端凭证许可类型(Grant Type:Client Credential)

后端系统之间需要直接通信,且本身并不代表某个特定用户,没有用户对客户端授权。客户端直接向授权服务器进行身份认证,而授权服务器给客户端颁发访问令牌。需要提供客户端id以及key(在对接前,手动申请)
其大致的请求如下:

POST /token
Host: localhost:9001
Accept: application/json
Content-type: application/x-www-form-encoded
grant_type=client_credentials&scope=foo%20bar&client_id=abcdsafdf&client_secret=qdfjadfj

3.4 断言许可类型

在断言许可类型下,客户端会得到一条结构化的且被加密保护的信息,叫作断言,使用断言向授权服务器换取令牌。这种许可类型只使用后端信道,与客户端凭据许可类型很相似,没有明确的资源拥有者参与。与客户端凭据流程不同的是,由此颁发的令牌所关联的权限取决于所出示的断言,而不仅仅取决于客户端本身。由于断言一般来自于客户端之外的第三方,因此客户端可以不知道断言本身的含义。
目前标准的断言格式:

  • 安全断言标记语言:SAML
  • JSON WEB TOKEN: JWT
POST /token HTTP/1.1
Host: as.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic b2F1dGgtY2xpZW50LTE6b2F1dGgtY2xpZW50LXNlY3JldC0x
grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer
&assertion=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6InJzYS0xIn0.eyJpc3MiOi
JodHRwOi8vdHJ1c3QuZXhhbXBsZS5uZXQvIiwic3ViIjoib2F1dGgtY2xpZW50LTEiLCJzY29wZSI....

3.5 资源拥有者许可凭证(不推荐使用)

如果资源拥有者在授权服务器上有纯文本的用户名和密码,那么客户端可以向用户索取用户的凭据,然后用这个凭据换取令牌,也叫作密码流程。
资源拥有者与之直接交互的是客户端,而不是授权服务器。这种许可类型只使用令牌端点,并且只通过后端信道通信。

3.6 授权许可类型如何选择

4 OAuth 安全相关

4.1 CSRF攻击

授权码许可和隐式许可类型中都可以使用的state 参数,这个参数是一个随机数,作为接口请求参数。客户端使用state参数来维持请求与回调之间状态的不透明值。授权服务器在将用户代理重定向回客户端时包含该值。应该使用这个参数,它可以防止CSRF(cross-site request forgery,跨站请求伪造)。后续有实际例子。

4.2 授权服务器安全

  • 授权码使用一次之后将其销毁。
  • 授权服务器应该采用精确匹配的重定向URI 校验算法,这是唯一安全的方法。
  • 完全按照OAuth 核心规范来实现授权服务器可能会导致它成为一个开放重定向器。如果这个重定向器能受到妥善的监控,则情况还好,但稍有不慎则会面临风险。
  • 留意在进行错误提示的过程中,信息有可能通过URI 片段或者Referrer 头部遭泄露。

4.3 授权码安全

授权码可以避免将令牌直接暴露给其他人,但是授权码仍可能白劫持。授权码本身是没有用的,特别是客户端拥有用于自身身份认证的密钥的情况下。然而,原生应用在客户端密钥方面存在特殊问题(APP可能需要把密钥写死在源代码中导致泄露);
解决办法:PKCE(Proof Key for Code Exchange)
(1)客户端创建并记录名为code_verifier的秘密信息
(2)客户端根据code_verifier计算code_challenge(例如:密码散列MAC)
(3)客户端请求授权服务器,并附带code_challenge以及code_challenge_method(计算方法,可选);
(4)授权服务器正常响应,并记录code_challenge和code_challenge_method(与授权码关联);
(5)客户端接收到授权码后,携带之前生成的code_verifier,执行令牌申请请求;
(6)授权服务器计算code_challenge,检测是否一致。

5 OAuth 令牌

OAuth 系统中的客户端无须了解令牌本身的任何信息。客户端需要知道的就是如何从授权服务器获取令牌以及如何在资源服务器上使用令牌。但是,授权服务器和资源服务器需要了解令牌的内容。授权服务器要知道如何生成令牌来颁发给客户端,资源服务器要知道如何识别并验证客户端发送过来的令牌。

  • OAuth 令牌可以具有有效期,可以支持撤回,也可以永久有效,或者根据情况将这些特性组合;
  • 令牌可以代表特定的用户或者系统中所有的用户,也可以不代表任何用户;
  • 令牌可以具有内部结构,可以是随机的无意义字符串,也可以被加密保护,甚至可以将这几项结合起来。
  • 授权服务器生成令牌之后,会将令牌值存储在磁盘上的共享数据库中(非必须)。当受保护资源从客户端收到令牌之后,它会在同一个数据库中查找令牌值,以确定令牌有效。这种令牌不携带任何信息,只是充当数据库查询的检索值。

5.1 结构化令牌:JWT

可参考内容:JSON Web Tokens – jwt.io

  • 优势:不向共享数据库查询,将所有必要的信息放在令牌内部,授权服务器可以通过令牌本身间接地与受保护资源沟通,而不需要调用任何网络API。
  • 劣势:颁发的令牌无法撤回。

5.1.1 JWT结构

JWT 的核心是将一个JSON 对象封装为一种用于网络传输的格式。整体机构通过句点分割,每个部分是由Base64URL编码的JSON对象,通过三个部分组成:

  • 头部(head):声明签名算法,以及负载类型
  • 负载(payload):用户数据
  • 签名(signature):对前两部分的签名
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

5.1.2 payload常用申明

5.1.3 JWT签名:JOSE

这里分享两种算法:
(1)HAMC算法:使用对称密钥,授权服务器和资源服务器都能够生成令牌,因为它们都拥有创建令牌所需的密钥。
(2)RSA算法:使用公钥加密的话,授权服务器拥有公钥和私钥,可用于生成令牌,而受保护资源则只能访问授权服务器的公钥,用于验证令牌。与HMAC相比,资源服务器就无法自己生成有效的令牌。
令牌请求资源服务器时,再对其进行验签,验签成功再解析payload部分。只有签名有效才能继续解析JWT 并检查其内容的一致性。如果所有检查都通过,就可以将它交给应用使用。

5.2 在线获取令牌信息:令牌内省

将令牌信息打包放入令牌本身也有其不足之处。为了包含所有必要的声明以及保护这些声明所需的密码结构,令牌尺寸会变得非常大。而且,如果受保护资源完全依赖令牌本身所包含的信息,则一旦将有效的令牌生成并发布,想要撤回会非常困难
令牌内省:授权服务器向客户端颁发令牌,客户端向受保护资源出示令牌,受保护资源则向授权服务器查询令牌状态(内省请求:资源服务器发送给授权服务器内省端点的表单形式的HTTP 请求,询问该令牌是否有效)
受保护资源在请求过程中需要向授权服务器进行身份认证(提供client_id以及client_secret),以便授权服务器知道是谁在询问,并可能根据询问者的身份返回不同的响应。内省协议只是要求受保护资源进行身份认证,并未规定如何认证。

POST /introspect HTTP/1.1
Host: localhost:9001
Accept: application/json
Content-type: application/x-www-form-encoded
Authorization: Basic cHJvdGVjdGVkLXJlc291cmNlLTE6cHJvdGVjdGVkLXJlc291cmNlLXNlY3JldC0x
 
token=987tghjkiu6trfghjuytrghj
 
// 内省请求的响应是一个JSON 对象,用于描述令牌信息。它的内容与JWT 的载荷相似
HTTP 200 OK
Content-type: application/json
{
    "active": true,
    "scope": "foo bar baz",
    "client_id": "oauth-client-1",
    "username": "alice",
    "iss": "//localhost:9001/",
    "sub": "alice",
    "aud": "//localhost:/9002/",
    "iat": 1440538696,
    "exp": 1440538996,
}

内省协议规范还在JWT 的基础上增加了几个声明定义,其中最重要的是active 声明。此声明告诉受保护资源当前令牌在授权服务器上是否有效,且是唯一必须返回的声明。
使用令牌内省会导致OAuth 系统内的网络流量增加。为了解决这个问题,允许受保护资源缓存给定令牌的内省请求结果。建议设置短于令牌生命周期的缓存有效期,以便降低令牌被撤回 但缓存还有效的可能性。

5.3 令牌生命周期管理

OAuth 令牌通常遵循一个可预测的生命周期。令牌由授权服务器创建,由客户端使用,并由受保护资源验证。它们可能会自行失效,也可能被资源拥有者(或者管理员)从授权服务器上撤回。对于JWT等格式的令牌无效,只能过期但无法失效。

5.3.1 令牌撤回协议

OAuth 令牌撤回是一个简单的协议,它让客户端可以很简洁地告诉授权服务器将本来有效的令牌撤回。客户端需要向一个专门的撤回端点发送附带身份认证的HTTP POST 请求,并将要撤回的令牌作为表单参数放入请求主体。

POST /revoke HTTP/1.1
Host: localhost:9001
Accept: application/json
Content-type: application/x-www-form-encoded
Authorization: Basic b2F1dGgtY2xpZW50LTE6b2F1dGgtY2xpZW50LXNlY3JldC0x
token=987tghjkiu6trfghjuytrghj

客户端身份认证时使用的凭据与在令牌端点上使用的凭据相同(判断是否时该客户端申请的令牌)。授权服务器会查询令牌值,如果找到令牌,会将它从存储令牌的地方删除,并返回响应告知客户端删除成功。如果授权服务器未找到令牌,或者不允许出示令牌的客户端撤回该令牌,授权服务器还是会返回操作成功,目的是不向客户端透露本不属于它的令牌信息。

5.3.2 刷新令牌

刷新令牌由授权服务器颁发给客户端。在OAuth 中,访问令牌随时可能失效。令牌有可能被用户撤销,也可能过期,或者其他系统导致令牌失效。访问令牌失效后,客户端在使用时会收到错误响应。当然,客户端可以再次向资源拥有者请求权限,也可以使用刷新令牌向授权服务器请求新的访问令牌。刷新令牌还可以让客户端缩小它的权限范围。如果客户端被授予A、B、C 三个权限范围,但是它知道某特定请求只需要A 权限范围,则它可以使用刷新令牌重新获取一个仅包含A 权限范围的访问令牌

6 可参考的线上OAuth接口文档

QQ互联:使用Authorization_Code获取Access_Token — QQ互联WIKI
WeChat OAuth: 准备工作|微信开放文档
支付宝 OAuth:用户授权
Google OAuth:Using OAuth 2.0 for Web Server Applications | Google Identity Platform
Github OAuth:Authorizing OAuth Apps – GitHub Docs

7 OAuth 线上个人OAuth申请

目前可知的,支付宝以及Github均支持以给个人身份申请接入,本次示例以Github为例。
支付宝申请地址:申请网页应用
Github申请地址:申请OAuth应用

填写完上述内容后,提交会获取到app_id以及app_secret,这样我们就可以开始对接了,具体内容可以参考第6部分的文档进行

8 Postman OAuth配置

因为第7部分我们已经申请到了github的OAuth2应用,则我们可以直接通过Postman进行快捷的接口测试,以github获取用户基本信息接口为例。
该接口请求为:GET //api.github.com/user

点开Get New Access Token进行配置,相关配置可以参考第六部分提供的文档

Tags: