OAuth 2.0 - 开放授权2.0标准

前言

1. 什么是OAuth

Oauth(Open Auth)是一个开放的网络授权协议,旨在提供"指定权限的访问"的能力,我们常说的“第三方登陆”、“SSO单点登录”,其内部原理大都涉及OAuth。

比如作为一个第三方App,需要访问你的微信个人信息,这个访问需要微信授权,但实际上你不需要提供你的微信用户名和密码,只是由微信授权提供一个安全的有时限的Access Token(访问令牌),第三方App服务只需要提供这个Access Token即可以达到获取微信个人信息数据;即使第三方存在信息安全泄露问题,微信的用户名账号密码还是安全的。

一个很好的类别: OAuth中的访问Token令牌,类似于高级轿车的代驾钥匙(仅可以用于移动轿车一小段距离提供泊车用,而不能开启后备箱等其他功能),当用户来到酒店时候,酒店服务人员提供泊车服务,需要向用户申请允许移动车辆的泊车钥匙,完成泊车服务;

OAuth不共享密码数据,而是使用授权令牌来证明消费者和服务提供商之间的身份。OAuth是一种身份验证协议,允许您代表您批准与另一个应用程序交互的应用程序,而不会泄露您的密码,其关键技术在于访问Token令牌的申请过程。

OAuth涉及三方:用户、消费者(第三方应用)、服务提供方,用户授权服务提供方,提供一个令牌给到服务消费方,来完成服务消费;

2. 工作流解说1:(用户在得到看到一个好的课程,准备分享到微信朋友圈)

2.1. 第一步,用户意图

  • Alice(用户):“嘿,得到,我想分享这本书到我的微信朋友圈!”
  • 得到(消费者):“好的,让我去找微信授权下!”

2.2. 第二步,服务消费者申请获取授权码/请求Token

  • 得到(消费者):“微信,我这边有个用户需要分享他的内容到朋友圈,可以申请一个**请求Token(授权码)**么?”
  • 微信(服务提供方):“好的,这个是请求Token令牌和一个请求签名秘钥,你让用户确认下”

请求签名秘钥用于防止CSRF请求伪造,消费者使用密钥对每个请求进行签名,以便服务提供者可以验证它实际上来自消费者应用程序。

2.3. 第三步,用户被重定向到服务提供方

  • 得到:”OK,Alice,我要把你送到微信授权下,拿着这个请求Token
  • Alice:“好的”

这一步存在被钓鱼的情况,恶意网站可能会伪造一个微信钓鱼站点,让你输入微信的账号、密码等。

2.4. 第四步,用户授权

  • Alice:“微信,我想批准得到获取请求Token,你帮授权下”
  • 微信:“好的,你这边准备授权得到 - 获取Alice“分享朋友圈”的权限么?
  • Alice:“是的,我授权得到拥有使用微信分享Alice朋友圈的权限”
  • 微信,“好的,微信已授权该请求Token令牌,拥有分享Alice朋友圈的权限,请告知得到”

2.5. 第五步,消费者活动访问令牌

  • 得到:“微信,我可以使用这个请求Token获取**访问token(Access Token)**么?”
  • 微信:“当然,这是你的访问token和秘钥”

2.6. 第六步,消费者访问受保护的资源

  • 得到:“微信,我想把这个链接分享到朋友圈,这是我的访问token令牌
  • 微信:“好的,访问Token令牌验证正确,分享内容到Alice朋友圈了”

3. 工作流解说2:(A网站获取Github用户基本数据)

  • A 网站让用户跳转到GitHub。
  • GitHub 要求用户登录,然后询问"A 网站要求获得 xx 权限,你是否同意?”
  • 用户同意,GitHub 就会重定向回 A 网站,同时发回一个授权码。
  • A 网站使用授权码,向 GitHub 请求令牌。
  • GitHub 返回令牌.
  • A 网站使用令牌,向 GitHub 请求用户数据。

4. OAuth 1.0与OAuth 2.0 差别

  • OAuth 2.0是OAuth协议的下一版本,但不向下兼容OAuth 1.0(如果您今天创建新应用程序,请使用OAuth 2.0)。
  • OAuth 2.0关注客户端开发者的简易性,同时为Web应用、桌面应用、手机和智能设备提供专门的认证流程,更快,更容易实现。
  • OAuth 2.0有六个流程用于不同类型的应用程序和要求(梳理下来有四类Access Token授权方式),OAuth令牌不再需要在2.0中的端点上加密,因为它们在传输过程中已加密(通过HTTPS启用签名机制)。

5. OAuth 2.0四类用户授权方式

虽然说四类,官方文档实际指明六授权类型,除下面四类:(授权码、隐式、密码、凭据)外,还有包括另外两类:(设备码、刷新token),我们这边指明与用户授权相关更紧密一些的,单独指明四类场景,刷新token是作为过期机制,设备码(web应用中开发中)涉及较少。

5.1. 前后端授权码方式 - 最常用,安全系数高

指的是第三方应用先申请一个授权码,然后再用该码获取令牌。(上面Alice、得到、微信的示例中,请求Token其实就是授权码,这种方式是最常用的流程,安全性也最高,它适用于那些有后端的 Web 应用。授权码通过前端传送,令牌则是储存在后端,而且所有与资源服务器的通信都在后端完成。这样的前后端分离,可以避免令牌泄漏。)

// 消费者请求获取授权码
https://b.com/oauth/authorize?response_type=code&client_id=CLIENT_ID&redirect_uri=CALLBACK_URL&scope=read
// 服务方授权码回调请求通知
https://a.com/callback?code=AUTHORIZATION_CODE
// 消费者通过授权码获取访问token
https://b.com/oauth/token?grant_type=authorization_code&code=AUTHORIZATION_CODE&client_id=CLIENT_ID&client_secret=CLIENT_SECRET&redirect_uri=CALLBACK_URL

5.2. 纯前端隐藏式 - 安全要求不高、token时效短的场景

针对纯前端应用,RFC6749 就规定了第二种方式,允许直接向前端颁发令牌。这种方式没有授权码这个中间步骤,所以称为(授权码)“隐藏式”(implicit)。

令牌的位置是 URL 锚点(fragment),而不是查询字符串(querystring),这是因为 OAuth 2.0 允许跳转网址是 HTTP 协议,因此存在"中间人攻击"的风险,而浏览器跳转时,锚点不会发到服务器,就减少了泄漏令牌的风险。

// 通过锚点纯前端交互
https://b.com/oauth/authorize?response_type=token&client_id=CLIENT_ID&redirect_uri=CALLBACK_URL&scope=read
https://a.com/callback#token=ACCESS_TOKEN

5.3. 纯后端凭证式 - 内部系统交互用

最后一种方式是凭证式(client credentials),适用于没有前端的命令行应用,即在命令行下请求令牌。

// 凭证式访问Token获取,client_id和client_secret用来让服务提供方确认消费方的身份。
https://b.com/oauth/token?grant_type=client_credentials&client_id=CLIENT_ID&client_secret=CLIENT_SECRET

5.4. 密码代理式 - 较少用

如果你高度信任某个应用,RFC6749 也允许用户把用户名和密码,直接告诉该应用。该应用就使用你的密码,申请并获取令牌,这种方式称为"密码式”(password)。

// 密码式获取Token
https://b.com/oauth/token?grant_type=password&username=USERNAME&password=PASSWORD&client_id=CLIENT_ID

6. 访问令牌使用

6.1. 基于令牌请求服务

// A网站拿到令牌以后,就可以向B网站的API请求数据了。
curl -H "Authorization: Bearer ACCESS_TOKEN" "https://api.b.com"

6.2. 基于刷新令牌,在过期前刷新token

OAuth 2.0 允许自动更新令牌(没有必要再重新走一次用户授权流程)。

服务提供者在提供请求Token(授权码)时候,会提供额外一个刷新Token,这样服务消费者(第三方应用)可以在令牌过期前,更新访问令牌(Access Token)。

// 刷新访问令牌
https://b.com/oauth/token?grant_type=refresh_token&client_id=CLIENT_ID&client_secret=CLIENT_SECRET&refresh_token=REFRESH_TOKEN

7. 完整的Github示例参考

参见:http://www.ruanyifeng.com/blog/2019/04/github-oauth.html

8. 针对Token和Token的管理

最长见到的就是,JWT令牌(RFC 7519),JSON网络令牌(JWT)是JSON的基于开放标准,用于创建访问令牌(Access Token),JWT被设计为紧凑以及URL-safe

JWT依赖于其他基于JSON的标准:JWS(JSON Web签名)RFC 7515和JWE(JSON Web加密)RFC 7516。

8.1. JWT结构

JWT的结构由头、有效载体、签名3部分组成。头包含签名算法、类型,有效载体包含一组常规+自定义的声明,签名部分通过Base64url Encoding对标头和有效负载进行编码,并将它们与句点分隔符连接在一起来计算签名。

签名在Chrome上面,可以基于JWT Debugger工具进行验签。

// 签名格式,
const token = base64urlEncoding(header) + '.' + base64urlEncoding(payload) + '.' + base64urlEncoding(signature)
// 生成格式
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dnZWRJbkFzIjoiYWRtaW4iLCJpYXQiOjE0MjI3Nzk2Mzh9.gzSraSYS8EXBxLN_oWnFSRgCzcmJmMjLiuyu5CSpyHI

8.2. 请求头标准字段

  1. typ: token类型,必须为JWT
  2. cty: 内容类型,可以忽略,用于嵌套签名或加密
  3. alg: 签名算法

8.3. 有效载体中的标准字段

  1. iss: 签发者
  2. sub: 标识jwt主题
  3. aud: 标识JWT的消费者
  4. exp: 标识JWT过期时间,时间戳格式
  5. nbf: 标识JWT不早于指定时间,时间戳格式
  6. iat: 标识JWT签发时间,时间戳格式
  7. jti: JWT ID,在不同的发行者之间,令牌的区分大小写的唯一标识符

8.4. JWT使用

在身份验证中,当用户使用OAuth成功授权后,返回的JWT令牌,并且必须在本地保存(通常在本地或会话存储中,但也可以使用cookie),而不是传统的创建会话的方法在服务器中并返回一个cookie。

每当用户想要访问受保护的路由或资源时,用户代理应该Authorization使用Bearer模式发送JWT,通常在头部中。标头的内容可能如下所示:

Authorization: Bearer eyJhbGci...<snip>...yu5CSpyHI

这是一种无状态身份验证机制,因为用户状态永远不会保存在服务器内存中。服务器的受保护路由将在Authorization标头中检查有效的JWT,如果存在,则允许用户访问受保护的资源。由于JWT是自包含的,所有必要的信息都在那里,减少了多次查询数据库的需要。

8.5. GO实现JWT的开源包

参考:https://github.com/dgrijalva/jwt-go

9. 参考

  1. OAuth官档:https://oauth.net/2/
  2. https://www.varonis.com/blog/what-is-oauth/
  3. https://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html
  4. http://www.ruanyifeng.com/blog/2019/04/oauth-grant-types.html
  5. http://www.ruanyifeng.com/blog/2019/04/github-oauth.html
  6. JWT wiki:https://en.wikipedia.org/wiki/JSON_Web_Token#cite_note-18
  7. JWT介绍:https://jwt.io/introduction