1. 引言-与OAuth2有关

  OAuth 2.0协议(RFC 6749)被广泛应用于互联网应用中,最常见的可能就是第三方授权登录应用了。在许多应用网站中用户登录时,可以使用支付宝、微信、QQ的已有账号进行登录,这些应用网站与阿里、腾讯共享了用户的信息和资源。
  OAuth 2.0协议的中心思想是让请求用户资源的一方(在RFC 6749中被称为client)向资源拥有方请求访问权限,请求访问权限的过程不是通过使用用户在资源拥有方的访问权证获得,而是将用户引向资源拥有方的授权服务器(Authorization Server),通过授权服务器授权后得到。
  在整个OAuth 2流程中,用户如果想让第三方(这里第三方指用户、用户注册方(支付宝、微信、QQ)之外的第三方)网站使用自己在支付宝、微信、QQ上资源(如账号、昵称、头像)等信息,就需要参与一个与支付宝、微信、QQ授权服务器交互的授权过程。
  图1以RFC 6749中授权码方式授权(Authorization Code Grant)方式描述了第三方网站的授权流程。
图1 授权流程
  我们以用户在K网站上使用QQ账号登录为例,用一个典型的开发流程解释图1:
  A. 用户在已进入K网站页面的浏览器上向QQ的授权服务发起用户资源请求:这里Client Identifier表示K网站在QQ授权服务器(Authorization Server,实际应用中QQ可能有多个服务器,这里抽象为一个授权服务器)上注册的标识,Redirection URI表示用户授权成功后K网站的重定向URI,K网站使用该URI来接收授权码(Authorization Code)。具体的请求参数和格式可参见RFC 6749。
  B. 浏览器跳入QQ授权服务器提供的授权页面,一般该页面提供两种方式供用户授权认证:用户名/密码登录方式和二维码扫码登录方式。
  C. 在获取到用户在步骤B中的授权认证后,QQ授权服务器重定向到步骤A中携带的重定向URI,并在重定向地址中携带授权码参数。
  D. K网站使用获取的授权码向QQ授权服务器请求Access Token,请求中带上的重定向URI要和步骤A中的重定向URI一致。
  E. QQ授权服务器对参数验证无误后,向K网站发送Access Token。
  上面的步骤中,步骤C后向重定向URI进行重定向的操作是由浏览器完成的,步骤D的操作在K网站的后台中完成。K网站的后台在步骤C后接收授权码的同时,也获得了浏览器的当前session(根据浏览器中的Cookie),在步骤E中得到Access Token后,K网站的后台向QQ授权服务器获取对应用户信息(昵称、头像等),将用户信息放入当前session中后,然后重定向到K网站的主页面,这时主页面上就显示QQ用户的信息了。

2. QQ扫码登录流程分析

  上一节中,我们描述了一个典型的基于OAuth2的QQ用户第三方登录流程。流程中最核心部分是步骤B,即如何引导用户向QQ的授权服务器提供授权认证。图2显示了QQ提供的认证登录页面。
图2 QQ认证登录页面
  QQ的授权服务器提供了两种授权认证方式:一种是账号密码登录方式,一种是二维码扫码方式。对于账号密码登录方式较好理解,因为用户的授权认证操作是在浏览器上进行的,可由浏览器直接和QQ授权服务器交互。而二维码扫码方式,情况就更复杂一些,因为多了一个角色参与,这个角色就是用户手机。实际上用户的授权认证操作是手机APP(移动QQ)上完成的,但是提供二维码的浏览器需要知道用户手机的扫描和认证结果,浏览器获取扫描和授权认证结果的途径是QQ授权服务器。
  反向工程前需要大胆设想,我们设想QQ二维码扫码登录时的大致流程是这样的:QQ认证登录页面获取QQ授权服务器产生的二维码;用户手机APP扫描该二维码,并在手机APP上确认授权,该授权信息存储在QQ授权服务器;QQ认证登录页面获取QQ授权服务器上的本次授权信息,向授权服务器发送获取授权码请求。
  在这个流程中,QQ认证登录页面和手机APP是分离的两个客户端,这两个客户端之间通过二维码联系在一起并共享二维码的扫描结果,QQ认证登录页面会使用什么手段从QQ授权服务器上获取手机APP上的授权认证结果是需要我们通过反向工程去探索的。

3. 反向工程解析

3.1. 流程分析

  在一个提供QQ、微信用户登录的第三方网站上执行QQ用户扫码登录,并采集流程中的HTTP消息,可整理出如图3所示的消息流程(图中消息10至11流程根据OAuth2协议设计,非反向工程获取)。
图3 信令流程
  根据图3,可描述为如下流程:
  1. 用户进入第三方网站主页,并通过主页上的登录按钮重定向到QQ认证登录页面。对应图3中消息1。
  2. QQ认证登录页面向QQ授权服务器请求生成二维码,该二维码有唯一标识。对应图3中消息2。
  3. 用户使用手机APP扫描该二维码,并在APP中认可授予的权限,该扫描用户信息与认可的授权信息保存到QQ授权服务器。
  4. QQ认证登录页面根据二维码标识不断轮询QQ授权服务器,看看是否有本次授权认证的信息。对应图3中消息3。
  5. QQ认证登录页面在QQ授权服务器上获取到本次已经授权的信息后,获取由服务器返回的登录用户信息。对应图3中消息4,5,6。
  6. QQ认证登录页面根据QQ授权服务器返回的信息向QQ授权服务器请求本次OAuth2的授权码,QQ授权服务器重定向到第三方应用注册的重定向(回调)URI。对应图3中消息7,8,9。
  7. 第三方网站后台根据授权码向QQ授权服务器获取Access Token,并根据Access Token获取用户信息。对应图3中消息10,11。需要注意的是:消息10和11流程根据OAuth2协议设计,非反向工程获取。
  8. 第三方网站后台向QQ认证登录页面返回重定向到第三方网站主页的请求,QQ认证登录页面重定向到第三方网站主页,这时该主页中已包含认证登录的QQ用户信息。

3.2. 消息分析

  通过上一节的消息流程分析,我们重点关注一下QQ扫码登录的相关消息。
  a) QQ认证登录页面请求,对应图3中消息1。请求格式如下:

GET /oauth2.0/show?which=Login&display=pc&client_id=xxx&redirect_uri=xxx&response_type=code&scope=get_user_info%2Cadd_share HTTP/1.1

  b) 二维码请求,对应图3中消息2。
  请求格式如下:

GET /ptqrshow?appid=xxxxxx&e=2&l=M&s=3&d=72&v=4&t=0.27689339003885305&daid=383&pt_3rd_aid=101372833

  该请求的回应中会设置cookie,作为该二维码的唯一标识,在后面的二维码状态查询中会用到。如下所示:

set-cookie: qrsig=Ht-tcKP8HsOucEnJLNd4RdqfbCwooJgQ3Z2Qjp5QApi0UoDGCIgPYu8VvQ6dAE8q;Path=/;Domain=ptlogin2.qq.com;

  c) 查询二维码扫描授权状态,对应图中消息3,4。
  请求格式如下:

    GET /ptqrlogin?u1=https%3A%2F%2Fgraph.qq.com%2Foauth2.0%2Flogin_jump&ptqrtoken=1441332869&ptredirect=0&h=1&t=1&g=1&from_ui=1&ptlang=2052&action=1-0-1533114559274&js_ver=10276&js_type=1&login_sig=EJ7zhLYsxLLTzyvFt57te*RfFnoefvlyL5EPQtIynibZvYDVTFwqp73mYhBZrw15&pt_uistyle=40&aid=716027609&daid=383&pt_3rd_aid=101372833&

  请求中cookie信息如下:

cookie: pt_login_sig=EJ7zhLYsxLLTzyvFt57te*RfFnoefvlyL5EPQtIynibZvYDVTFwqp73mYhBZrw15; pt_clientip=78f767d654ee2f3d; pt_serverip=612d0af17164d8cd; pt_local_token=55538618; uikey=a295803bda0158cc66a043eadd466549793036cdc18cf00adc324fe62c3dfdbb; pt_guid_sig=f1fb580c215810be91bdc81c31b0dbbf864e8530ff5bbe12a752ec79aa84096f; pgv_pvi=4603649024; pgv_si=s8250232832; _qpsvr_localtk=0.6218837724703585; qrsig=Ht-tcKP8HsOucEnJLNd4RdqfbCwooJgQ3Z2Qjp5QApi0UoDGCIgPYu8VvQ6dAE8q

  cookie中的pt_login_sig和qrsig作为查询参数,保证一次生成的二维码只绑定一个账号。
  请求的结果中包含当前二维码的状态,如下所示:

    ptuiCB('67','0','','0','二维码认证中。(1759531849)', '')

  如果用户已经在手机APP上授权认证,请求的回应中会返回用户本次的登录授权标识,放在头部的set-cookie中,如下所示(其中出现了扫描用户的QQ号):

set-cookie: pt_guid_sig=ad3f568b9d1d721593fde21261e9110921367f8e918ee573d0290ddde03fe5a9;Expires=Fri, 31 Aug 2018 09:06:36 GMT;Path=/;Domain=ptlogin2.qq.com;
set-cookie: uin=o0545602528;Path=/;Domain=qq.com;
set-cookie: skey=@Xb8aQtVvd;Path=/;Domain=qq.com;
set-cookie: superuin=o0545602528;Path=/;Domain=ptlogin2.qq.com;
set-cookie: pt2gguin=o0545602528;Expires=Tue, 19 Jan 2038 03:14:07 GMT;Path=/;Domain=qq.com;
set-cookie: superkey=iXw-EiDFksitURyqw9dWBfOH7OIowHjqdYZPbu2U2iQ_;Path=/;Domain=ptlogin2.qq.com;HttpOnly;
set-cookie: pt_recent_uins=a82cd26c99dc9b53af5f945be98ce71176658eb32199788d1c479b025d8b0f7a7872d4e9cf761581865bdcff1157eae7684374b9637a77f2;Expires=Fri, 31 Aug 2018 09:06:36 GMT;Path=/;Domain=ptlogin2.qq.com;HttpOnly;
set-cookie: ETK=;Path=/;Domain=ptlogin2.qq.com;
set-cookie: RK=jcDM6tiRa/;Expires=Tue, 19 Jan 2038 03:14:07 GMT;Path=/;Domain=qq.com;
set-cookie: ptnick_545602528=e88b8fe5b79ee7a791e8bebe2de5bca0e587af;Path=/;Domain=ptlogin2.qq.com;
set-cookie: ptcz=cc528e09239038601ee0ab4b7665c65404e847c2963a19d71a266c628029ee22;Expires=Tue, 19 Jan 2038 03:14:07 GMT;Path=/;Domain=qq.com;
set-cookie: ptcz=;Expires=Thu, 01 Jan 1970 00:00:00 GMT;Path=/;Domain=ptlogin2.qq.com;
set-cookie: airkey=;Expires=Thu, 01 Jan 1970 00:00:00 GMT;Path=/;Domain=qq.com;
set-cookie: supertoken=3346071835;Path=/;Domain=ptlogin2.qq.com;

  d) 更新页面上的cookie,对应图3中消息5,6。返回的请求头中包含了一系列的set-cookie与登录用户有关,这里就不贴出来了。
  e) 向QQ授权服务器获取授权码,对应图3中消息7,8。
  请求格式如下:

POST /oauth2.0/authorize

  请求中表单数据如下:

response_type: code
client_id: xxxxxxxxx
redirect_uri: http://www.XXX.com/oauth/callback/type/qq.html
scope: get_user_info,add_share
state: 
switch: 
from_ptlogin: 1
src: 1
update_auth: 1
openapi: 80901010
g_tk: 1934165869
auth_time: 1533114565802
ui: EBF619C8-8BF2-4882-9D8D-8D7E6C0D05E1

  包含的cookie信息如下:

Cookie: ui=EBF619C8-8BF2-4882-9D8D-8D7E6C0D05E1; pgv_pvi=4603649024; pgv_si=s8250232832; _qpsvr_localtk=0.6218837724703585; pt2gguin=o0545602528; uin=o0545602528; skey=@Xb8aQtVvd; RK=jcDM6tiRa/; ptcz=cc528e09239038601ee0ab4b7665c65404e847c2963a19d71a266c628029ee22; p_uin=o0545602528; pt4_token=kME-OHaPJ3rFmtsksNxnUcWYTP6JEWRvd2EX8DHyfAE_; p_skey=oA8SEkIxHh7-2v-XneForJ9gUH4PTgROUPCQ6YdpUzI_

  QQ授权服务器根据请求中表单和cookie中的标识确定本次扫描确认结果,返回授权码。回应消息格式如下:

HTTP/1.1 302 Moved Temporarily
Server: tws
Date: Wed, 01 Aug 2018 09:06:37 GMT
Content-Type: text/html
Content-Length: 0
Connection: keep-alive
Keep-Alive: timeout=50
Content-Encoding: gzip
Location:http://www.XXX.com/oauth/callback/type/qq.html?code=653D0AAEA1EF7D12A4AF99AD4CDC4D41

4. 小结

  我们通过反向工程结合OAuth2流程分析了在第三方网站上使用QQ扫码登录的功能,分析了消息流程与相关消息,明确了大致的实现思路。消息中有些参数(如cookie中的一些键值)是QQ特有的,含义也无法根据流程推断出来,这就需要腾讯的兄弟去给大家解释了。