浅笑博客
我可是发自内心地爱着这一名为人生的游戏。所以,无论何时,我也要以玩家的身份继续下去。
浅笑博客
PHP实现QQ扫码登录以及验证接口
PHP实现QQ扫码登录以及验证接口

本文根据根教书先生QQ空间扫码登录改写为QQ个人中心扫码登录,并进行了个人修改和优化

参考自QQ 扫码登陆详解 – 可做登陆或者授权程序下载 – 教书先生个人博客

教书先生博客中写的是根据QQ空间扫码登录,扫码成功后只能获取登录成功后的QQ号,且目前有时获取二维码无法显示。为了能够更稳定的获取二维码, 获取更加详细的QQ登录信息以能够验证登录信息是否正确。参考教书先生博客提供的源码进行了学习并按QQ个人中心的扫码登录写了个php以及验证程序。


说下扫码登录的主要原理:

1.请求获取二维码。同时获取二维码签名值。

2.通过二维码签名值请求获取扫码结果。如等待扫码、等待确认、登录成功等状态。

原理很简单,我们打开QQ网站(id.qq.com),分析请求。很容易发现有一个ptqrshow的请求,返回的是一张二维码图片。同时响应头中的cookie中有一个qrsign的值。

http://blog.qianxiao.fun/wp-content/uploads/2020/04/图片.png

通过测试发现,参数基本固定,t为随机数。因此模拟它是没有问题,具体在下面代码中有注释。

接下来会发现每3秒会有一个ptqrlogin的请求,显然为获取扫码结果的请求。

http://blog.qianxiao.fun/wp-content/uploads/2020/04/图片-1.png

通过多次测试请求发现,该请求需要请求cookie中含有qrsign值才能正常访问,否则403 Forbidden。同时发现请求参数大多数固定,login_sig可为空, action 为”0-0-时间戳”格式, ptqrtoken 为签名值,具体签名算法参考自教书先生博客。

然后就是php实现上述请求了,代码如下:

<?php
#接受所有请求源
header('Access-Control-Allow-Origin:*');
#指定返回数据为json utf-8
header('Content-type:application/json; charset=utf-8');

#post参数是否为空
function ispostnull($a){
	if(empty($_POST[$a]) || !isset($_POST[$a]) || $_POST[$a]=='' || $_POST[$a]==null){
		return true;
	}
	return false;
}

/*
 * 如果post参数中有qrsig,则判断为获取扫码结果,否则判断为获取二维码
 * (也可在此加时间戳有效验证以及签名验证)
 */
if(ispostnull('qrsig')){
	//获取二维码
	echo json_encode(getqrcode());
}else{
	$qrsig = $_POST['qrsig'];
	//获取二维码扫码结果
	echo json_encode(getresult($qrsig),JSON_UNESCAPED_UNICODE);
}

/**
 * 获取二维码请求
 */
function getqrcode() {
    $qrcode = array();
    $api = 'https://ssl.ptlogin2.qq.com/ptqrshow?appid=1006102&e=2&l=M&s=3&d=72&v=4&daid=1&pt_3rd_aid=0&t=0.1415855' . time();
    $paras['header'] = 1;#使返回时包含响应头(qrsig在响应头中)
    $ret = get_curl($api, $paras)['allbody'];
    preg_match('/qrsig=(.*?);/', $ret, $matches);
    preg_match_all('/ (\d){3}/', $ret, $Conlen);
    $arr = explode('com;', $ret);
    $qrcode['qrsig'] = $matches[1];
    $qrcode['data'] = base64_encode(trim($arr['1']));
    $qrcode['code'] = 1;
    return $qrcode;
}

/**
 * 获取扫码结果请求
 */
function getresult($qrsig) {
    $ret = array();
    $api = 'https://ssl.ptlogin2.qq.com/ptqrlogin?u1=' . urlencode('https://id.qq.com/index.html') . '&ptqrtoken=' . getqrtoken($qrsig) . '&ptredirect=1&h=1&t=1&g=1&from_ui=1&ptlang=2052&action=0-1-' . time() . '&js_ver=20032614&js_type=1&login_sig=&pt_uistyle=40&aid=1006102&daid=1';
    $paras['cookie'] = 'qrsig=' . $qrsig . ';';
    $paras['refer'] = 1;
    $paras['header'] = 1;
    $allbody = get_curl($api, $paras);
    $body = $allbody['body'];
    if (preg_match("/ptuiCB\('(.*?)'\)/", $body, $arr)) {#正则匹配ptuiCB()括号中的内容
        $r = explode("','", str_replace("', '", "','", $arr[1]));#用','分割
        #第一个值为返回代码
        if ($r[0] == 0) {
            preg_match('/uin=(\d+)&/', $body, $uin);
            $ret['code'] = 1;
            $ret['data']['uin'] = $uin[1];#正则解析到QQ号
            $ret['data']['nick'] = $r[5];#昵称
            $responseheader = $allbody['header'];
            preg_match('/Set-Cookie: skey=(.*?);/', $responseheader, $skey);
            $ret['data']['skey'] = $skey[1];
            preg_match('/Set-Cookie: superkey=(.*?);/', $responseheader, $superkey);
            $ret['data']['superkey'] = $superkey[1];
            //上面代码为在请求后通过正在匹配以及字符串处理获取我们需要的值,如QQ号、昵称、skey、superkey 。再次访问$r[2]获取pskey
            $paras['header'] = 1;
            $pskeyresponseheader = get_curl($r[2], $paras)['header'];
            preg_match('/Set-Cookie: p_skey=(.*?);/', $pskeyresponseheader, $pskey);
            $ret['data']['pskey'] = $pskey[1];
            $ret['msg'] = 'QQ登录成功';
            //也可在此直接进行业务逻辑(无需验证),因为这里为从QQ官方的请求获取的信息
        } elseif ($r[0] == 65) {
            $ret['msg'] = '登录二维码已失效,请刷新重试!';
            $ret['code'] = -4;
        } elseif ($r[0] == 66) {
            $ret['msg'] = '请使用手机QQ扫码登录';
            $ret['code'] = 0;
        } elseif ($r[0] == 67) {
            $ret['msg'] = '正在验证二维码...';
            $ret['code'] = 2;
        } else {
            $ret['msg'] = '未知错误001,请刷新重试!';
            $ret['code'] = -5;
        }
    } else {
        $ret['msg'] = '未知错误002,请刷新重试!';
        $ret['code'] = 1;	
    }
    return $ret;
}

/*
 * curl模拟请求函数
 */
function get_curl($url, $paras = array()) {
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    //关闭https验证
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    $httpheader[] = "Accept:*/*";
    $httpheader[] = "Accept-Encoding:gzip,deflate,sdch";
    $httpheader[] = "Accept-Language:zh-CN,zh;q=0.8";
    $httpheader[] = "Connection:close";
    curl_setopt($ch, CURLOPT_HTTPHEADER, $httpheader);
    if ($paras['ctime']) { // 连接超时
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, $paras['ctime']);
    }
    if ($paras['rtime']) { // 读取超时
        curl_setopt($ch, CURLOPT_TIMEOUT_MS, $paras['rtime']);
    }
    if ($paras['post']) {
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $paras['post']);
    }
    if ($paras['header']) {
    	#是否返回响应头
        curl_setopt($ch, CURLOPT_HEADER, true);
    }
    if ($paras['cookie']) {
        curl_setopt($ch, CURLOPT_COOKIE, $paras['cookie']);
    }
    if ($paras['refer']) {
        if ($paras['refer'] == 1) {
        	#这样可以设置请求头的referer,防止请求失败
            curl_setopt($ch, CURLOPT_REFERER, 'https://xui.ptlogin2.qq.com/cgi-bin/xlogin?pt_disable_pwd=1&appid=1006102&daid=1&style=23&hide_border=1&proxy_url=https://id.qq.com%2Flogin%2Fproxy.html&s_url=https://id.qq.com/index.html');
        } else {
            curl_setopt($ch, CURLOPT_REFERER, $paras['refer']);
        }
    }
    if ($paras['ua']) {
        curl_setopt($ch, CURLOPT_USERAGENT, $paras['ua']);
    } else {
    	#设置默认ua
        curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36");
    }
    if ($paras['nobody']) {
    	#设置是否不要响应体
        curl_setopt($ch, CURLOPT_NOBODY, 1);
    }
    curl_setopt($ch, CURLOPT_ENCODING, "gzip");
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    $ret = curl_exec($ch);
    if($paras['header']){
        $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
        $return_result['allbody'] = $ret;
        $return_result['header'] = substr($ret, 0, $header_size);
        $return_result['body'] = substr($ret, $header_size);
        
    }else{
        $return_result = $ret;
    }
    curl_close($ch);
    return $return_result;
}

/** id.qq.comToken算法 ,同QQ空间Token算法*/
function getqrtoken($qrsig) {
    $len = strlen($qrsig);
    $hash = 0;
    for ($i = 0; $i < $len; $i++) {
        $hash += (($hash << 5) & 2147483647) + ord($qrsig[$i]) & 2147483647;
        $hash &= 2147483647;
    }
    return $hash & 2147483647;
}

function retn($code,$str){
    exit(json_encode([
        "code"=>$code,
        "msg"=>$str
    ],JSON_UNESCAPED_UNICODE));
}

然后就是验证,所谓验证是什么呢?就是比如给你一组登录信息,比如QQ号、skey、pskey,来判断是否有效或是否过期。主要用途如可在上面的请求中,在扫码登录成功后获取到QQ号以及各种登录信息key值后,你可以暂时保存它们,如在cookie中。之后可以通过验证来判断是否有效以达到一定有效时间内免再次扫码。当然也要注意保存信息如cookie的安全,以免泄露而造成的不必要的损失。

对于验证我是用在id.qq.com中找到的一个请求实现的。这个请求通过在请求头中放置有效的skey、pskey,能够访问对应QQ号的简单信息,如昵称、生日,以此来做验证。

http://blog.qianxiao.fun/wp-content/uploads/2020/04/图片-2.png
http://blog.qianxiao.fun/wp-content/uploads/2020/04/图片-3.png

该请求通过测试发现,该请求请求参数中u为QQ号,c固定,t为随机数。

同时该请求需要请求头必须设置 :

Referer:https://id.qq.com/index.html

否则403 Forbidden,同时请求头中设置cookie如下:

Cookie: skey=@AbwXZxlQ4;uin=o1540223760; p_uin=o1540223760; p_skey=hACkHFgQIp-wUl2TfuFgUykm0XiQQl6YCb8zHt0H3cs_

其中skey、uin、p_uin、p_skey分别对应skey、o+QQ号、 o+QQ号、 pskey。

可通过返回数据的ec值0还是1判断是否登录成功。

php实现如下:

<?php
#接受所有请求源
header('Access-Control-Allow-Origin:*');
#指定返回数据为json utf-8
header('Content-type:application/json; charset=utf-8');	

#post参数是否为空
function ispostnull($a){
	if(empty($_POST[$a]) || !isset($_POST[$a]) || $_POST[$a]=='' || $_POST[$a]==null){
		return true;
	}
	return false;
}

/*
 * (也可在此加时间戳有效验证以及签名验证)
 */
if(ispostnull('qq')||ispostnull('skey')||ispostnull('pskey')){
	retn(-1,"参数为空");
}else{
    $qq = $_POST['qq'];
    $skey = $_POST['skey'];
    $pskey = $_POST['pskey'];
	$mycookie = 'uin=o'.$qq.'; p_uin=o'.$qq.'; skey='.$skey.'; p_skey='.$pskey.';';
	$headers[] = "referer:https://id.qq.com/index.html";
	$headers[] = 'cookie:'.$mycookie ;
	$ret = curlRequest('https://id.qq.com/cgi-bin/info?u='.$qq.'&c=1&t=0.230889'.time(),$headers);
	$ret = json_decode($ret,true);#将返回结果解析为json
	#根据ec值0还是1判断是否登录有效。
	if($ret['ec']==0){
		//key验证成功
		retn(1,"验证成功");
	}else{
		retn(0,"验证失败,key已过期或不正确");
	}
}

function curlRequest($url, $headers=null, $connect_timeout = 1000, $read_timeout = 2000)
{
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_HEADER, false);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    if ($headers) {
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);//设置请求头
    }
    if (defined('CURLOPT_CONNECTTIMEOUT_MS')) {
        $options[CURLOPT_CONNECTTIMEOUT_MS] = $connect_timeout;
        $options[CURLOPT_TIMEOUT_MS]        = $connect_timeout + $read_timeout;
    } else {
        $options[CURLOPT_CONNECTTIMEOUT] = ceil($connect_timeout / 1000);
        $options[CURLOPT_TIMEOUT]        = ceil(($connect_timeout + $read_timeout) / 1000);
    }
    $content = curl_exec($ch);
    if (false === $content) {
        return false;
    }
    $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    if ($http_code !== 200) {
        return false;
    }
    return $content;
}

function retn($code,$str){
    exit(json_encode([
        "code"=>$code,
        "msg"=>$str
    ],JSON_UNESCAPED_UNICODE));
}
?>

最近写的API接口平台:浅笑API接口平台,我也将本节的接口也放在上面的。有API文档和写的demo,如果不会使用的,可以过去查看。[传送门]


最后分享一个在线接口测试网站:http://coolaf.com/

可自定义请求头和cookie,方便进行接口请求测试。

发表评论

textsms
account_circle
email

浅笑博客

PHP实现QQ扫码登录以及验证接口
本文根据根教书先生QQ空间扫码登录改写为QQ个人中心扫码登录,并进行了个人修改和优化 参考自:QQ 扫码登陆详解 - 可做登陆或者授权程序下载 - 教书先生个人博客 教书先生博客中写的…
扫描二维码继续阅读
2020-04-13