第 87 天:Python Web开发 OAuth2.0 简介

随着互联网的发展,各种应用相互交叉,到处需要用户登录,信息安全成为了不可回避的问题,应用需要扩展,用户需要更好的体验,信息需要更安全的保障,为了满足这些需求,互联网技术不断推陈出新,从通信安全,到各种协议框架,有无数的解决方案。

其中 OAuth 框架是很闪耀的一个,一经推出,就得到各大互联网公司的积极响应,到2010年推出了 OAuth 2.0, 不但修补了 1.0 的安全漏洞,而且简化了授权流程,得到更广泛的应用,成为了主流。从 PC 到 Web,从移动端到物联网,越来越多的应用构建在 OAuth 框架之上,那么 OAuth 是什么呢?

OAuth 是什么

OAuth 框架提供了一种认证和授权机制,可以让用户将其受保护的资源授权给其他应用来访问或者使用。

阮一峰老师对 授权有个形象比喻:授权机制相当于你给快递员一个临时密码(授权),快递员可以使用这个密码打开小区门禁,将快递送到你家门口,而后临时密码将失效(详见参考链接)。

这个例子中,你就是 用户,小区是 受保护的资源,快递员是其他应用(第三方应用)。

如果没有这个机制:

  • 要么你就得去小区门口拿快递,不方便,
  • 要么就得告诉所有可能给你送快递的快递员门禁密码,不安全

有了 OAuth 框架(协议),既方便,又安全

OAuth2.0

这里不打算介绍 OAuth 1.0,原因是:

  • OAuth 1.0 存在安全漏洞
  • OAuth 1 和 后面的 OAuth 1a 交流流程比较复杂
  • OAuth 2.0 安全性好,应用更广泛

如果想了解更多关于 OAuth 的知识,请访问参考链接

角色

OAuth2.0 实际上就是让第三方服务获得用户在资源服务器上的授权的过程,会涉及到 4 种角色

  • 资源拥有者(Resource owner),即用户
  • 认证服务器(Authorization server),用来认证用户凭证,颁布授权码的服务器
  • 资源服务器(Resource Server),存放用户受保护的资源的服务器
  • 第三方应用(Client),也称之为客户端(后续皆称 客户端),需要得到用户授权,以便访问用户受保护的资源的应用程序

不是任何客户端都能得到授权的,在开通 OAuth 授权之前,需要先到认证服务器或者资源服务器上注册,注册成功会得到 appid 和 app_secret,用来向认证服务器表明应用的身份

角色之间关联

了解了 OAuth2.0 框架中的主要角色,有必要了解下角色之间关联关系

  • 认证服务器 和 资源服务器:通常来说,认证服务器和资源服务器同属于一个服务商,它们就有天生的关联,而且是内部的安全的,甚至它们可以部署在同一个服务器(Web 服务器)上
  • 用户 和 服务商:对于像 Github、微信这样的知名应用,用户会主动在这些应用或服务上注册,填写的资料信息,设置的昵称,产生的文章、上传的照片,等等将成为用户的资源,这些资源被存放在资源服务器上
  • 客户端 和 服务商:客户端,即第三方应用,要从服务商的资源服务器中获取数据,给用户提供额外服务,必须在服务商处注册,提供应用的基本信息,认证信息,服务域名,申请用户授权的范围、甚至企业资质(例如申请微信公众号的企业服务)等等,申请通过后,服务商会返回 appid 和 app_secret,作为客户端和服务商的交互凭证
  • 用户 和 客户端:客户端提供了特别的服务,可以吸引到用户,为了让用户体验更好,引导用户通过授权的方式,从而获取授权范围内的用户信息,例如 Openid,作为用户在客户端上的唯一标识,从而建立起和用户的关联

至此,四个角色之间的关联就建立好了,下面开始介绍具体的授权方式

基本授权流程

没有比图更能说明白流程的,借用 RFC6749 文档中的图:

授权流程图
  • A 客户端,向用户发出授权请求
  • B 用户同意或者拒绝客户端的授权请求,假设是同意
  • C 客户端拿着用户的授权请求认证服务器做认证
  • D 如果 C 通过认证,认证服务器将返回 Access Token,即可以访问资源的令牌
  • E 客户端使用 Access Token 请求资源服务器上的资源
  • F 资源服务器验证了 Access Token 后,返回受保护的资源

流程中最核心的是让客户端获得 Access Token,之后在访问受保护资源时,就不需要用户反复授权了

Access Token 显然不是用户在资源服务器上的密码,是有认证服务器颁发的,那么也可以被销毁

Access Token 和之前课程中的 JWT 是类似的,实际上 JWT 是 OAuth 认证的一个特例

授权模式

根据授权流程,OAuth2.0 定义了 4 种针对不同应用场景的授权模式

  • 授权码模式(authorization code)
  • 简化模式(implicit)
  • 密码模式(resource owner password credentials)
  • 客户端模式(client credentials)

授权码模式

授权码模式是最完整,安全性最高的授权模式,也是最常用的一种模式,其特点是通过客户端的后台服务器与认证服务器交互,如图:

授权码模式流程图

注意:上图中的步骤 A, B, C 在通过用户代理端( User-Agent 一般指浏览器)时,被拆分成了两部分

  • A 用户访问客户端的客户端,后者将前者导向认证服务器
  • B 用户确定是否授权给客户端
  • C 假设用户给予授权,认证服务器将用户导向客户端事先指定的"重定向URI"(Redirection URI),同时附上一个授权码
  • D 客户端收到授权码,通过后台服务器,附上早先的"重定向URI",向认证服务器申请令牌。这一步对用户不可见
  • E 认证服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)

接下来说明一下过程中所包含一下参数

不同认证服务器上的参数名称有可能不同,但含义相同,例如 client_id 一般被 appid 代替

步骤 A,客户端申请认证的 URI,包含以下参数:

  • response_type:表示授权类型,必选项,此处的值固定为 code,因为需要先获取授权码
  • client_id:表示客户端的ID,必选项,是在认证服务器分配给客户端的id,即 appid
  • redirect_uri:表示重定向URI,可选项
  • scope:表示申请的权限范围,可选项
  • state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值

步骤 C,认证服务器回应的 URI,包含以下参数:

  • code:表示授权码,必选项。该码的有效期应该很短,通常设为 10 分钟,客户端只能使用该码一次,否则会被认证服务器拒绝。该码与客户端 ID 和重定向 URI,是一一对应关系
  • state:如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数

步骤 D,客户端向认证服务器申请令牌的 HTTP 请求,包含以下参数:

  • grant_type:表示使用的授权模式,必选项,此处的值固定为 authorization_code
  • code:表示上一步获得的授权码,必选项
  • redirect_uri:表示重定向 URI,必选项,且必须与 A 步骤中的该参数值保持一致
  • client_id:表示客户端 ID,必选项

步骤 E,认证服务器发送的 HTTP 响应,包含以下参数:

  • access_token:表示访问令牌,必选项
  • token_type:表示令牌类型,该值大小写不敏感,必选项,可以是bearer类型或mac类型
  • expires_in:表示过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间
  • refresh_token:表示更新令牌,用来获取下一次的访问令牌,可选项
  • scope:表示权限范围,如果与客户端申请的范围一致,此项可省略

认证服务器会以 JSON 的形式返回 access_token 数据,且不允许做缓存,以提高安全性

简化模式

简化模式不需要通过客户端后台服务器,直接在浏览器中向认证服务器申请令牌,跳过了授权码这个步骤,因此得名。所有步骤在浏览器中完成,令牌对访问者是可见的,且客户端不需要认证,如图:

简化模式流程图
  • A 客户端将用户导向认证服务器
  • B 用户决定是否给予客户端授权
  • C 假设用户给予授权,认证服务器将用户导向客户端指定的重定向URI,并在URI的Hash部分包含了访问令牌 access_token
  • D 浏览器向资源服务器发出请求,其中不包括上一步收到的Hash值
  • E 资源服务器返回一个网页,其中包含的代码可以获取Hash值中的令牌
  • F 浏览器执行上一步获得的脚本,提取出令牌
  • G 浏览器将令牌发给客户端

接下来说明一下过程中所包含一下参数

步骤 A,客户端发送 HTTP 请求,包含的参数:

  • response_type:表示授权类型,此处的值固定为 token,必选项
  • client_id:表示客户端的ID,必选项
  • redirect_uri:表示重定向的URI,可选项
  • scope:表示权限范围,可选项
  • state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值

步骤 C,认证服务器回应客户端的 URI,包含以下参数:

  • access_token:表示访问令牌,必选项。
  • token_type:表示令牌类型,该值大小写不敏感,必选项
  • expires_in:表示过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间
  • scope:表示权限范围,如果与客户端申请的范围一致,此项可省略
  • state:如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数

有个需要注意的地方,步骤 C,返回的 access_token 放在重定向 URL 的 Fragment 中,即锚点中, # 后面,例如

http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA &state=xyz&token_type=example&expires_in=3600

因为锚点中的内容不会发送给后台,从而减少了一次数据传输,降低了一定风险

对于简化模式获得的 access_token 有效期很短,一般是会话级的,即当会话结束时就失效

密码模式

密码模式中,用户向客户端提供自己的用户名和密码,客户端使用这些信息,向"服务商提供商"索要授权。

在这种模式中,用户必须把自己的密码给客户端,但是客户端不得储存密码(既然用户信任你,你就必须兑现这个承诺)。

密码模式的特性决定,需要用在用户对客户端高度信任的情况下,比如客户端是操作系统的一部分,或者由一个著名公司出品,而认证服务器只有在其他授权模式无法执行的情况下,才能考虑使用这种模式

这里只简单介绍下,不做做详细讲解,如有兴趣了解,可以查阅文末参考

客户端模式

客户端模式指客户端以自己的名义,而不是以用户的名义,向"服务提供商"进行认证。严格地说,客户端模式并不属于 OAuth 框架所要解决的问题

在这种模式中,用户直接向客户端注册,客户端以自己的名义要求"服务提供商"提供服务,其实不存在授权问题

客户端模式,就像二道贩子(只为借用比喻,并无贬义),将原始服务包装后,再提供给最终用户,常见于多租户的 Saas 系统,例如统一提供支付通道、处理 GPS 信息等

这里也只简单介绍下,如有兴趣了解,可以查阅文末参考

刷新 access_token

在 授权码模式中,授权服务器可以会同时返回 refresh_token,用来在 access_token 过期前,重新获取新的access_token,不需要用户重新确认授权,有助于提高用户体验

在 access_token 过期前,客户端可用 refresh_token 向授权服务器发送请求,例如,假设 b.com 是授权服务器地址,请求大体是:

https://b.com/oauth/token? grant_type=refresh_token& client_id=CLIENT_ID& client_secret=CLIENT_SECRET& refresh_token=REFRESH_TOKEN
  • grant_type: 授权类型,值为 'refresh_token'
  • client_id: 客户端 id,即第三方应用在授权服务器上注册被分配的 id
  • client_secret: 客户端和授权服务器通行的密钥,由授权服务器颁发,在特殊需要确认的情况下需要作为验证条件
  • refresh_token: 用户获取新的 access_token 的 refresh_token

安全的 OAuth2.0

上面较为详细的讲述了 OAuth2.0 框架,了解了在开放网络中如何安全的获取用户授权的技术细节,但再完善的交互方案、再复杂的严密的通信过程,都避免不了中间人攻击,当客户端和认证服务器之间通过 http 协议交互数据时,会被截取通信内容,从而获得用户的授权,这是不能接受的,所以 OAuth2.0 需要建立在 https 协议上,将通信内容加密,最大程度的防止信息被窃取。

如果 OAuth2.0 框架用在敏感信息交互上时,必须使用 https 协议确保安全,但并不是说只能支持 https,对于非敏感数据,或者不重要的授权,可以使用 http 协议作为通信方式

总结

本节课程着重介绍了 OAuth2.0 授权框架,从它的作用,到具体的技术细节,做了较为详细的讲述,如果需要用安全的授权,需要将通信建立在 https 协议之上。由于 OAuth 概念较多,流程复杂,这节没有涉及到具体的编程实践,下一节,我们以 Github 为例,使用之前介绍过的 Authlib 模块,用 Flash 实现一个第三方应用,作为实践,敬请期待

参考

  • RFC 6749: https://tools.ietf.org/html/rfc6749
  • 比喻: http://www.ruanyifeng.com/blog/2019/04/oauth_design.html
  • http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html
  • https://docs.authlib.org/en/stable/basic/oauth2.html
系列文章
第 86 天:Python SQLAlchemy
第 85 天:NumPy 统计函数
第 84 天:NumPy 数学函数
第 83 天:NumPy 字符串操作
第 82 天:Python Web 开发之 JWT 简介
第 81 天:NumPy Ndarray 对象及数据类型
从 0 学习 Python 0 - 80 大合集总结
(0)

相关推荐