OAuth 2.0深入了解:以微信开放平台统一登录为例

7,463 阅读5分钟

一、什么是OAuth 2.0

简单的说,OAuth 2.0是个授权框架。它定义了第三方应用如何通过用户授权,来访问用户的受限资源。

举个例子,个人网站要支持微信账号登陆,微信开放平台的授权登陆就用到了OAuth 2.0。

OAuth 2.0 涉及的关键参与方有:

  1. Resource Owner:资源所有者。这里指微信用户。
  2. Third-party application:第三方应用。这里指个人网站。。
  3. Authorization server:授权服务器。这里指微信开放平台的授权服务。
  4. Resource server:资源服务器,用来存储、获取用户资源。这里指的是微信开放平台的服务器。

二、OAuth 2.0 基本流程

OAuth 2.0 主要包含两个关键步骤:

  1. 第三方应用取得用户授权。
  2. 第三方应用访问用户资源。

其中,“取得用户授权“是流程重点,最终取得的授权凭证叫做access token。如下图所示:

如上图所示,access token的获取分为两步:

  1. 获取授权码code,这是临时授权凭证:步骤A、B、C、D。
  2. 通过code交换access token,这是正式授权凭证:步骤E、F。

获取 access token 的细节是本文重点,下一节会进行介绍。

三、如何获取access token

有多种方式可以获取access token,这里主要介绍最常见授权码模式(Authorization Code Grant)。

授权码模式 流程如下:

授权码模式

跳过具体细节,看下各步骤具体做了什么:

  1. 步骤A、B:第三方应用取得用户授权。
  2. 步骤C:第三方应用取得授权码(authorization code)。
  3. 步骤D:第三方应用请求授权凭证(access token)。
  4. 步骤E:第三方应用获得授权凭证(access token)。

User-Agent:前端开发的同学应该不陌生,大部分时候指的就是浏览器。

接下来,稍微详细点讲解各个步骤:

1、请求用户授权

第三方应用,将资源所有者导向一个特定的地址,并在地址里带上如下信息:

  • response_type:必选,请求类型。这里固定为"code"。
  • client_id:必选,标识第三方应用的id。很多地方也用apppid来代替。
  • redirect_uri:可选,授权完成后重定向的地址。当取得用户授权后,授权服务会重定向到这个地址,并在地址里带上授权码。
  • scope:可选,第三方请求的资源范围。比如是想获取基本信息、敏感信息等。
  • state:推荐,用于状态保持,可以是任意字符串。授权服务会原封不动地返回。

对于redirect_uri是可选的,大家可能会有疑惑。在实际中,redirect_uri 一般在应用后台就完成了填写和验证,因此可以是选填的。

2、用户授权返回

资源所有者,同意授权第三方应用访问受限资源后,请求返回,跳转到 redirect_uri 指定的地址。

地址中带了如下信息:

  • code:必选,授权码。后续步骤中,用来交换access token。
  • state:必选(如果授权请求中,带上了state),这里原封不动地回传。

3、请求access token

第三方应用,向授权服务请求获取access token。请求参数包括:

  • grant_type:必选,许可类型,这里固定为“authorization_code”。
  • code:必选,授权码。在用户授权步骤中,授权服务返回的。
  • redirect_uri:必选,如果在授权请求步骤中,带上了redirect_uri,那么这里也必须带上,且值相同。
  • client_id:必选,第三方应用id。

4、返回access token

请求合法且授权验证通过,那么授权服务将access token返回给第三方应用。

关键返回字段:

  • access token:必选,访问令牌,第三方应用访问用户资源的凭证。
  • expires_in:推荐,access token的有效时长。
  • refresh token:可选,更新access token的凭证。当access token过期,可以refresh token为凭证,获取新的access token。

例子如下:

     HTTP/1.1 200 OK
     Content-Type: application/json;charset=UTF-8
     Cache-Control: no-store
     Pragma: no-cache

     {
       "access_token":"2YotnFZFEjr1zCsicMWpAA",
       "token_type":"example",
       "expires_in":3600,
       "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
       "example_parameter":"example_value"
     }

四、以微信授权为例

以微信开放平台统一登录为例,更多细节可参考 官方文档

下图为微信统一登录的时序图:

步骤分解如下:

1、请求用户授权:步骤2、3、4

带上appid、redirect_uri、response_type、scope、state。其中:

  • appid:应用id,就是前面提到的client_id。
  • redirect_uri:授权回调的地址,在微信管理后台填写。
  • response_type:响应类型,固定为"code"。
  • scope:授权许可范围,固定为"snsapi_login"。
  • state:可选,授权服务回传。

2、用户授权返回:步骤5

用户同意授权,重定向到 redirect_uri, 并返回临时票据code。如下所示:

redirect_uri?code=CODE&state=STATE

3、请求access token

应用拿到临时票据后,用临时票据去换取真实票据 access token。所需参数如下:

  • appid:必选,应用id。
  • secret:必选,应用秘钥,在微信后台生成。
  • code:必选,前面获取的授权码。
  • grant_type:必选,值固定为"authorization_code"

4、返回access token

微信后台经过验证,确认请求合法后,将access token返回给第三方应用。

返回例子如下:

{ 
    "access_token":"ACCESS_TOKEN", 
    "expires_in":7200, 
    "refresh_token":"REFRESH_TOKEN",
    "openid":"OPENID", 
    "scope":"SCOPE",
    "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
}

除前面提到的access_token、refresh_token、expires_in,这里还返回了 openid、unionid,这两者是用户信息,微信体系特有的,不展开。

五、为什么不直接返回access_token

在授权码模式下,授权服务先返回授权码code给第三方应用,第三方应用再利用授权码来换取access token。

为什么不直接返回access token呢?

主要是出于安全方面的考虑。

假设第三方应用、授权服务不直接通信,中间隔了一层代理。同时,第三方应用采用HTTP协议,那么,恶意代理就可以窃取access token。

这就是所谓的中间人攻击。

因此,采用了通过code来交换access token的方式,来增加安全性。并且,不能将access token直接给到用户侧。

相对于用户侧网络环境的复杂性,应用自身服务端的网络环境相对更可控些。但这并不意味着就绝对安全。

如果微信开放平台的接口是基于HTTP的,那么不单access token,连secret也有被截获的的风险。

六、相关链接

OAuth 2.0规范
https://tools.ietf.org/html/rfc6749

为什么需要authorization_code
https://stackoverflow.com/questions/13387698/why-is-there-an-authorization-code-flow-in-oauth2-when-implicit-flow-works-s

微信网页授权
https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842

关于作者:程序猿小卡,前腾讯高级工程师,现任前海云汉金融科技前端技术负责人。专注技术架构、技术分享、项目管理。