基本概念

什么是 OAuth 2.0?

OAuth 2.0 的全称是 Open Authorization 2.0,是一种授权机制,简单说就是:允许用户授权第三方应用程序访问他们存储在另一服务提供商上的资源,而无需将用户密码直接暴露给第三方应用。 与以往的授权方式不同之处是 OAuth 2.0 的授权不会使第三方触及到用户的帐号信息(如用户名与密码),即第三方无需使用用户的用户名与密码就可以申请获得该用户资源的授权。

这种说法还是比较抽象,为了便于理解,在网上找到了一个通俗易懂的例子:

有一个"云冲印"的网站,可以将用户储存在 Google 云盘的照片冲印出来,为了使用“云冲印”的服务,用户就必须授权其访问自己存储在 Google 云盘上的照片数据,只有得到了用户的授权,Google 才会允许访问。

那么,该如何实现这个过程?

如果使用传统的账号密码认证,那么用户就必须把自己的账号和密码交给“云冲印”服务,这样显然是不安全的,并且权限控制也难以控制,如果需要取消访问 Google 云盘的权限,就必须重置密码。

OAuth 的出现就是为了解决这个问题,其授权的方式是:由“云冲印”服务向 Google 发起授权请求,重定向到 Google 的认证页面,由用户进行确认授权,用户授权确认之后 Google 会下发一个临时令牌给“云冲印”服务,在一定有效期内,“云冲印”服务就可以凭借这个令牌访问用户存储在 Google 云盘上的照片数据了,整个过程用户的账号和密码都无需交给“云冲印”服务,确保了用户的账号安全。

OAuth 2.0 是 OAuth 协议的第二版,旨在简化客户端开发,并显著提高网络应用的可用性和安全性。日常使用的快捷登录,如 QQ 登录、微信登录等,都是 OAuth 2.0 的应用。

一些名词的解释

在 OAuth 2.0 认证机制中,有一些术语名词,还是要解释理解一下。

  • 客户端(Clien):需要访问用户资源的第三方应用,如上述所说的“云冲印”;
  • 资源所有者(Resource Owner):通常指的就是用户;
  • 服务提供商(Service Provider):提供用户资源存储的供应商,如上述所说的 Google;
  • 授权服务器(Authorization Server):服务提供商用于发放令牌的服务;
  • 资源服务器(Resource Server):服务提供商用于存储和提供访问能力的服务;
  • 访问令牌(Access Token):客户端用于访问用户资源的凭证,访问资源时使用这个向服务提供商进行认证;
  • 刷新令牌(Refresh Token):客户端用于在访问令牌过期时向服务提供商换取新的访问令牌的凭据;

授权机制

OAuth 2.0 提供了 4 种授权机制:

  1. 授权码模式
  2. 隐式授权模式
  3. 密码模式
  4. 客户端凭证模式

其中,应用最广泛、安全效果最好的就是授权码模式,其流程图如下:

在网上发现了一个网站,能够逐步观察 OAuth 2.0 的认证流程: https://www.oauth.com/playground/

首先先注册一个客户端:

得到以下信息:

client_id		DUn5jHV6xm91TJAIMRUEGskz
client_secret	dzKvtJW29lZIgK9DWOKDbev9Gj4GLDfkcomz4ugqvKdjXyis

username		combative-dogfish@example.com
password		Long-Panther-85

然后选择授权码模式(Authorization Code):

可以看到这里构造了一个 HTTP 请求,就是由客户端向授权服务器发起的授权请求,注意其中的几个参数:

参数 作用
response_type 向授权服务器说明,本次授权采用授权码模式
client_id 客户端应用的唯一标识符,用于确保请求由可信的客户端应用发送,客户端必须在授权服务器注册,并获取一个唯一的 client_id
redirect_uri 授权服务器在用户完成授权操作后,将用户重定向到此 URI,这是客户端应用在授权服务器注册时提供的回调地址,必须确保 redirect_uri 与注册时提供的一致,防止重定向攻击
scope 指定客户端请求访问的资源范围和权限等级
state 客户端生成的一个唯一的随机字符串,在请求中包含此字符串,授权服务器会原样返回这个字符串,客户端在收到重定向时对比这个值,确保请求没有被篡改,防止 CSRF 攻击

点击 Authorize 之后,便会要求输入账号密码,这里就是授权服务器重定向到用户登录页面,由用户进行授权确认了:

用户身份验证通过之后,授权服务器会返回一个授权码和 state 参数,这个 state 参数就是客户端在发起授权请求时携带的随机值,在这里需要校验是否一致,以防止 CSRF 攻击的产生:

显然这里是一致的,所以确认,继续下一步:

校验通过之后,客户端就要使用授权服务器返回的授权码换取访问令牌,这里同样构造了一个 HTTP 请求,注意其中的参数:

参数 作用
grant_type 指定授权类型为 authorization_code
client_id 客户端应用的唯一标识符,用于确保请求由可信的客户端应用发送
client_secret 客户端的密钥信息,用于验证客户端的身份
redirect_uri 客户端应用在授权服务器注册时提供的回调地址,必须确保 redirect_uri 与注册时提供的一致,防止重定向攻击
code 上一步中授权服务器下发的授权码,用于换取访问令牌,使用一次后即失效

客户端发起换取访问令牌的请求后,授权服务器就会校验一系列客户端参数,确保请求来源可信,并且身份验证通过,当这一切都满足后,就会下发最终的访问令牌和刷新令牌:

客户端就可以使用访问令牌来访问资源服务器中用户的资源了。

安全风险

虽然 OAuth 2.0 本身就是一种安全协议,但是既然它应用在互联网中,就必然面临着互联网常见的安全风险,例如:CSRF、XSS 等,OAuth 2.0 本身也有一定的安全问题,包括授权码泄漏,重定向伪造等。

CSRF

CSRF(客户端请求伪造),指的是通过在客户端注入恶意代码或在 URL 中插入恶意参数,执行用户无意的请求操作,冒用用户身份在用户当前登录的应用中执行未授权的操作,而用户完全不知情。

CSRF 本质上就是利用了用户已经授权的身份,并借用他们的权限在用户不知情的情况下完成某些操作。

在 OAuth 2.0 场景下,CSRF 主要影响两个环节:

  1. 授权服务器重定向到认证页面
  2. 客户端使用授权码换取访问令牌

主要攻击方式是:攻击者构造一个恶意链接,并诱导用户点击,点击后用户将被重定向到授权服务器上,用攻击者控制的 redirect_uri 来窃取授权码,然后使用这个授权码来获取访问令牌。

防御方式

在 OAuth 2.0 中,提供了一个可选参数 state,该参数是一个由客户端随机生成的字符串,在向授权服务器发起请求时携带,授权服务器会原样返回该字符串,客户端在接收到响应时,会对 state 参数进行校验,如果和生成的随机值不一致,则拒绝处理。因为字符串的随机性,攻击者难以预测客户端生成的 state 参数,因此即使诱导用户发起授权请求,最终也会被客户端拒绝处理,从而防御 CSRF 的产生。

XSS

XSS(跨站脚本攻击)指的是通过在网页中注入恶意脚本,这些恶意脚本会在用户浏览网页时被触发执行,攻击者借此窃取会话信息、重定向到恶意网站等。

在 OAuth 2.0 流程中,URL 参数通常传递授权码、状态信息、访问令牌等,如果网站存在 XSS 漏洞,攻击者就可以通过注入恶意代码,窃取客户端等授权码和访问令牌等敏感信息。

防御方式

  1. 过滤和校验用户输入的参数,防止特殊字符的出现;
  2. 启用严格的 CSP 策略,严格限制加载的脚本来源,确保其可信;

硬编码泄漏

部分客户端的开发者安全意识不强,将客户端 ID 和 Secret 硬编码到程序中,攻击者可以通过逆向反编译等手段获取程序源码中的 ID 和 Secret 值,从而伪装成客户端获取授权服务器发放的授权码和访问令牌。