浅笑博客
人看不到现实的本来面目。只能看到想看的、想拥有的现实。
浅笑博客
PHP实现QQ扫码登录并验证是否加群
PHP实现QQ扫码登录并验证是否加群

为了验证用户是否加入了某群,我准备做一个QQ扫码验证是否加群的接口,为什么一定要再扫一次码?因为这需要登录到QQ群官网首先获取skey、pskey,以调用获取群列表接口获取群列表,进而才能判断是否加了某群。

由于以前做过一次我的QQ中心(id.qq.com)的扫码验证登录(传送门),有所经验。所以仿之前的,略加修改,就实现了QQ群官网的扫码验证登录。以获取到用户的uin(qq号)、skey和pskey。

这里skey和pskey有什么用呢?通过在QQ群官网-群管理-成员管理界面可以爬到一个获取用户群列表的接口,而这个接口只需QQ号、skey、pskey即可请求获取用户群列表,包含了用户了所有群,且有分类(加入的群、管理的群,创建的群),并且可以得到所有群的群主qq。

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

对上述请求进行模拟测试时发现,除了需要uin、skey、pskey,还必须携带一个请求参数bkn:

http://blog.qianxiao.fun/wp-content/uploads/2020/07/图片-2-1024x618.png

于是看一下这个bkn是怎么来的,刷新网页,在所有请求中进行资源中查找bkn,发现其他请求的bkn都是请求时携带的,没有搜到bkn是响应中或是响应cookie中的,但发现了一个包含bkn的js,那说明bkn是通过前端js调用生成的。

http://blog.qianxiao.fun/wp-content/uploads/2020/07/图片-3-1024x618.png

对上述js内容进行美化后(这里的js没有加密,所以方便处理。不过就算加密了,我们也可以解密一步一步分析,如有机会,我会再分享一篇关于js解密的文章),发现如下位置是bkn的生成:

http://blog.qianxiao.fun/wp-content/uploads/2020/07/图片-4-1024x618.png

其实就是简单对skey进行了一个加密后所取到的值。

为了方便,我就不再在php中模拟实现这个算法了,直接js前端请求时,计算得到后传入后端。

这是我写的后端代码:

<?php
header('Access-Control-Allow-Origin:*');
header('Content-type:application/json; charset=utf-8');

function ispostnull($a){
	if(empty($_POST[$a]) || !isset($_POST[$a]) || $_POST[$a]=='' || $_POST[$a]==null){
		return true;
	}
	return false;
}

if(ispostnull('qqgroup')||ispostnull('qq')||ispostnull('skey')||ispostnull('pskey')||ispostnull('timestamp')||ispostnull('parsign')){
	retn(-1,"参数为空");
}else{
	$qqgroup = $_POST['qqgroup'];
	$qq = $_POST['qq'];
	$skey = $_POST['skey'];
	$pskey = $_POST['pskey'];
	$nowtime = time();
	$requesttime = $_POST['timestamp'];
	$parsign = $_POST['parsign'];
	if ($nowtime-$requesttime>60 || $requesttime-$nowtime>60) {
		retn(-2,"Error");
	}else{
		if(strnatcasecmp(sha1("qqgroup=".$qqgroup."&qq=".$qq."&skey=".$skey."&pskey=".$pskey."&timestamp=".$requesttime.'qqlogin'), $parsign)){
			retn(-3,"Error");
		}else{
			$mycookie = 'uin=o'.$qq.'; p_uin=o'.$qq.'; skey='.$skey.'; p_skey='.$pskey.';';
			$params["cookie"] = $mycookie;
			$params["post"] = "bkn=".$_POST['bkn'];
			$ret = get_curl('https://qun.qq.com/cgi-bin/qun_mgr/get_group_list',$params);
			$ret = json_decode($ret,true);
            if($ret['errcode']!=0){
                retn(-1,"请求群列表接口错误");
            }else{
                $isjoin = false;
                $count_json = count($ret['join']);
                for ($i = 0; $i < $count_json; $i++){
                    if($ret['join'][$i]['gc']==$qqgroup){
                        $isjoin = true;
                        break;
                    }
                }
                if(!$isjoin){
                    $count_json = count($ret['manage']);
                    for ($i = 0; $i < $count_json; $i++){
                        if($ret['manage'][$i]['gc']==$qqgroup){
                            $isjoin = true;
                            break;
                        }
                    }
                    if(!$isjoin){
                        $count_json = count($ret['create']);
                        for ($i = 0; $i < $count_json; $i++){
                            if($ret['create'][$i]['gc']==$qqgroup){
                                $isjoin = true;
                                break;
                            }
                        }
                    }
                }
                if($isjoin){
                    retn(1,"验证成功,QQ(".$qq.")已加入QQ群(".$qqgroup.")");
                }else{
                    retn(0,"验证失败,QQ(".$qq.")用户没有加入QQ群(".$qqgroup.")");
                }
                
            }
		}
	}
}

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: https://xui.ptlogin2.qq.com/cgi-bin/xlogin?pt_disable_pwd=1&appid=715030901&daid=73&hide_close_icon=1&pt_no_auth=1&s_url=https%3A%2F%2Fqun.qq.com%2Fmember.html%23
            curl_setopt($ch, CURLOPT_REFERER, 'https://xui.ptlogin2.qq.com/cgi-bin/xlogin?pt_disable_pwd=1&appid=715030901&daid=73&hide_close_icon=1&pt_no_auth=1&s_url=https%3A%2F%2Fqun.qq.com%2Fmember.html%23');
        } else {
            curl_setopt($ch, CURLOPT_REFERER, $paras['refer']);
        }
    }
    if ($paras['ua']) {
        curl_setopt($ch, CURLOPT_USERAGENT, $paras['ua']);
    } else {
        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;
}

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

前面是一些参数验证,如时间有效性验证和签名验证,主要是后面模拟请求那个官方的群列表请求接口获取所有群后依次遍历判断是否加了某群。

然后前端将验证的qq群号、以及qq号、skey、pskey传入即可返回验证结果:

var qqgroupnum = value;
//获取当前时间戳
var timestampvar = Date.parse(new Date())/1000;
//参数签名
var parsignvar = CryptoJS.SHA1("qqgroup="+qqgroupnum+"&qq="+data.data.uin+"&skey="+data.data.skey+"&pskey="+data.data.pskey+"&timestamp="+timestampvar+"qqlogin")+"";
//ajax请求进行验证
var bkn = function() {
    for (var e = data.data.skey, t = 5381, n = 0, o = e.length; n < o; ++n) t += (t << 5) + e.charAt(n).charCodeAt();
    return 2147483647 & t
}();
$.ajax({
    url: 'http://api.qianxiao.fun/qqlogin/test/verify.php',
    aycnc: false,
    type: 'POST',
    data: {bkn:bkn,qqgroup:qqgroupnum,qq:data.data.uin,skey:data.data.skey,pskey:data.data.pskey,timestamp:timestampvar,parsign:parsignvar},
    dataType: 'json',
    success: function (data) {
        console.log(data);
        $("#res").text(data.msg);
        if(data.code == 1){
          layer.tips(data.msg,'.layui-layer-btn0',{tips: [4, '#78BA32']})
        }else{
          layer.tips(data.msg,'.layui-layer-btn0',{tips: [4, '#DC143C']})
        }
    },
    error: function () {
      layer.tips('验证请求请求错误','.layui-layer-btn0',{tips: [4, '#78BA32']})
    }
});

示例如下:

http://api.qianxiao.fun/qqlogin/test/demo.html

如有任何建议或疑问,欢迎评论交流,或给我发送邮件。

浅笑原创,如有转载请注明“来自浅笑博客-PHP实现QQ扫码登录并验证是否加群,http://blog.qianxiao.fun/?p=1137。“

发表评论

textsms
account_circle
email

浅笑博客

PHP实现QQ扫码登录并验证是否加群
为了验证用户是否加入了某群,我准备做一个QQ扫码验证是否加群的接口,为什么一定要再扫一次码?因为这需要登录到QQ群官网首先获取skey、pskey,以调用获取群列表接口获取群列表,进而才…
扫描二维码继续阅读
2020-07-28