反向工程解析QQ扫码登陆的OAuth2流程

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用户的信息了。html

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上的受权认证结果是须要咱们经过反向工程去探索的。api

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,做为该二维码的惟一标识,在后面的二维码状态查询中会用到。以下所示:cookie

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

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

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信息以下:app

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做为查询参数,保证一次生成的二维码只绑定一个帐号。
  请求的结果中包含当前二维码的状态,以下所示:ide

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特有的,含义也没法根据流程推断出来,这就须要腾讯的兄弟去给你们解释了。