认证授权那点事儿 —— OAuth 2.0

OAuth 2.0 —— 开放授权协议,对应的规范文件RFC-6749早在2012年便成形,所以这并不是一个新的技术(你问我为啥研究这个,我也想吟一首诗啊。。。组织上就是这样决定的),但由于其必不可少的价值,在今天的网络上已经得到了广泛的应用。

OAuth2.0认证是要在不同的应用之间打通互信,互信的目的是为了实现一定程度上的用户数据分享,没数据的一方到有数据的一方拿数据,并且在时间尺度上,数据的分享是受控的。

比如你在微信上打开小程序,小程序会向微信索要你的基本信息;比如你用马克飞象作为印象笔记的客户端,马克飞象会向印象笔记索要你的账户信息,以及阅读、创建、删除、修改笔记等的权限;比如你用github账号登录leetcode,后者会去前者索要你的账户信息 ……

在这些场景里都涉及三方:用户、没数据的应用、有数据的应用(授权、资源)。OAuth2.0规范就规定了三方如何交互,完成权限的授予,获取数据的过程。整体逻辑过程如下图

认证授权那点事儿 —— OAuth 2.0

整个授权框架中包含了4种授权模式:

  • 授权码模式
  • 隐式许可模式
  • 密码模式
  • 客户端模式

在详述4种模式之前,首先需要注意,第三方应用需要先到资源持有应用处注册身份,提交回调URI,注册成功后,得到标识身份的 Client ID 和 Client Secret。资源持有者也可以趁这个阶段对第三方应用进行安全审核。
认证授权那点事儿 —— OAuth 2.0

Client Type, 根据客户端与授权服务器进行安全认证的能力,分为 Confidential(机密客户端)和 Public(公开客户端)。
Client IDClient Secret,应用的身份标识。
Registered Redirect URIs,回调地址,后面会用到。

授权码模式

授权码模式是四种模式中最严密的,主要用于web应用,我们来看看整体逻辑图
认证授权那点事儿 —— OAuth 2.0

下面我按图上的顺序一步步说明。

  1. 我在浏览器上打开了LeetCode网页,并且发现可以用github账户来登录,于是,我点击了章鱼标志。这个时候,就被LeetCode导向了github的认证页面,并且携带了几个必要参数。

    Client ID,表明第三方应用自己的身份,即这个请求是从LeetCode过来的。
    Scope,表明要请求哪些用户信息,用户名、密码、邮箱、头像……
    Redirect URI,应用注册时填入的回调URI之一,授权服务器会检查,必须严格匹配。
    State,一个不容意被猜到的随机数,用于第3步确认收到的code是自己请求的,避免 CSRF 攻击。

  2. 现在打开了github的认证页面(如果没有事先登录,github会要求我先登录github账号),github页面提示是否授权给LeetCode,点击是,则进入下一步;否则,流程结束。

  3. github调用回调URI(这个URI就是最初注册时提交的URI之一),传回授权码,把state也传回。这时,授权码就到了第三方应用的后台服务器。这一步之后的交互,在浏览器上就看不到了,由两个应用的后台服务器完成。

    Authorization Code,授权码,一个临时的随机串。
    State,第1步传过来的state。

  4. 拿到code,并比对了state后,LeetCode服务器就拿着这个code去换访问令牌。

    Client ID & Client Secret,表明自己的身份。
    Redirect URI,获取授权码时提交的回调地址,授权服务器会检查是不是一致。
    Authorization Code,前面拿到的授权码。

  5. 授权服务器认证了客户端身份,并检查回调地址通过后,调用回调URI回传访问令牌。

    Token Type,值为 Bearer Token。
    Access Token,访问令牌,访问资源的凭据。
    Scope,权限范围。
    Expires In,令牌有效时间,单位秒。
    Refresh Token,可选参数,用户更新访问令牌。

  6. 现在LeetCode可以拿着令牌去github获取我的账户信息了,从浏览器上看,我顺利登入了LeetCode。

回过头来看一下,为什么先发一个授权码,不直接发访问令牌?

因为直接发令牌,那么到达用户浏览器的就是令牌。而拿着令牌去访问资源是不需要认证的(别问为什么,就是这样的设计和用法)。如果本地环境不可信,那么让第三方应用服务器和资源服务器去交互,完成code到token的转换是更安全的选择。

即使Authorization Code被另一个应用窃取,并且该应用在授权服务器上合法,也不能使用这个授权码,因为授权服务器会检查带着授权码过来换token的应用是不是最初申请该授权码的应用。

而且,授权码只能被使用一次,当在本地被窃取了以后,如果你使用一次code,攻击者使用一次code,不管谁先谁后,第二次使用code将导致授权服务器终止该code及已下发的token的有效性。

授权码模式被使用的最多,以上讲解只是列出了规范中的主要参数,编码时具体实现还要看各厂是如何落地规范的,可以搜github、微信、google等的接口文档。

隐式许可模式

隐式许可模式和授权码模式最大的不同就是没有授权码交互这一环节,它主要用于依附于浏览器的驻于用户侧的应用,如JavaScript应用。
认证授权那点事儿 —— OAuth 2.0

还是从图说起

  1. 和授权码模式一样。

  2. 和授权码模式一样。

  3. 用户授权通过后,调用回调URI,直接就传回访问令牌。

    Redirect URI,指向第三方应用的资源服务器。
    Fragment with Access Token, etc.,在URI的fragment部分(即#后的部分)携带访问令牌等参数,只会被留在浏览器本地,里面包含了和授权码模式第5步中相同参数。

  4. 访问第三方应用的资源服务器,请求从fragment参数中提取访问令牌的脚本,由于http规范,请求中不会携带fragment部分。

  5. 返回解析脚本,并提取出访问令牌。

  6. 拿着访问令牌去获取资源。

这里看到4、5的时候,困惑了很久,为什么要特地去第三方应用的服务器上取解析脚本,有什么安全考量。SO上某个5K声望的仁兄的回答是:没看出什么考虑,只是在本地放脚本和去服务器取脚本中选了一种方式。我看,是不是因为反正都会用到 Redirect URI,就顺便取个脚本,不然糟蹋了这一来一回。最后访问令牌留在了驻用户侧的应用里。考虑到这种模式不严瑾,行业最佳实践建议采用授权码模式或PKCE模式。

密码模式

密码模式就是用户把用户名和密码都放心地给到第三方应用(如山一般厚重的信任),第三方应用以用户的名字去认证并获取资源。规范中规定了应用不应该保存用户输入的用户名和密码。此种模式多见于资源服务器自家的应用。
认证授权那点事儿 —— OAuth 2.0

客户端模式

第三方应用证明自己的身份,以自己的身份去获取一些公共的资源。这种模式我就不细解释了,参数用途和前面的一致。
认证授权那点事儿 —— OAuth 2.0

基本就是这样了,下次再讲讲 PKCE、Device Code。

如理解有误,欢迎各位老铁拍砖。



参考:
https://oauth.net/2/
https://tools.ietf.org/html/rfc6749
https://getpocket.com/a/read/2216790858

相关推荐