QQ登录互联SDK详解 & 优化.

2013-12-26日, 腾讯更新了QQ登录组件2.1, 可以见官方下载链接: QQ click, 重点就是修复一重要的安全问题, 并且在邮件中群发了此消息, 相信许多用户会着手升级与调试. SDK下载后, 就开始表达腾讯的马虎了. 
   
官方md5:1A180F106EF785AE4A04E91EA1882A69
我下载了十多次, 检测md5: D34936AFE7694070EE493B890392C58E


特别是文件md5值, 我们见腾讯的游戏下载, 重要软件都会公布md5值, 防止网络运营商中转离线资源, 可这php sdk我失望了, 下载的包, 跟官方描述的md5是不符合的, 这是描述未更新过来的情况? 对于文件md5值, 个人也把意见提交给QQ旋风, 这是非常重要的. 希望腾讯要认真了.


解压到文件夹后, 发现这版本似乎做了一个demo版本出来, 给普通用户一个直观的界面操作. 如:
QQ登录互联SDK详解 & 优化._第1张图片

这是北大青鸟用户元旦PHP作业吗? 好吧, 腾讯你伤了我的心.


当没有勾选授权列表项目时, 会提示如下错误:

Warning: implode(): Invalid arguments passed in D:\xampps\htdocs\Connect2.1\install\index.php on line 29

所以我觉得input type应该是radio才对, 一定要选择一个项目.



核心文件为API目录下的qqConnectAPI.php, 所有调试均会经过这个php, 共有三行有用代码, 包括开始session, 引入配置文件, 引入QC核心库. 或许官方不想考虑更深层的整合问题, 我觉得在这儿开启session是有问题, 这使得流程更加不好控制.



class目录下有四个文件:
  ErrorCase.class.php (错误类), Recorder.class.php (用户配置类), URL.class.php (url类库存), Oauth.class.php(验证类) , QC.class.php(调试类, 中转功能)



Recorder.class.php 文件初始化方法:

    public function __construct(){
        $this->error = new ErrorCase();

        //-------读取配置文件
        $incFileContents = file(ROOT."comm/inc.php");
        $incFileContents = $incFileContents[1];
        $this->inc = json_decode($incFileContents);
        if(empty($this->inc)){
            $this->error->showError("20001");
        }

        if(empty($_SESSION['QC_userData'])){
            self::$data = array();
        }else{
            self::$data = $_SESSION['QC_userData'];
        }
    }


当你配置了appid, appkey后, 就会产生一个inc.php文件, 里面就你的一些配置, 用的是json格式, 我惊呆. 通过这儿,释放进对象, 个人觉得这个类完全没多少意义. 



const VERSION = "2.0";



当我看到这一行代码时, 我终于明白为什么腾讯需要"360护航"了, 亲, 你到底是2.0还是2.1?

QQ互联何需如此复杂, 下面提供我优化的单一php互联版. 方便大家集成进系统.

类库文件代码:
<?php
/* PHP SDK
 * @version 2.1
 * @author [email protected]
 * @copyright © 2013, Tencent Corporation. All rights reserved.
 */

class Oauth{
    
    const VERSION = "2.1";
    const GET_AUTH_CODE_URL = "https://graph.qq.com/oauth2.0/authorize";
    const GET_ACCESS_TOKEN_URL = "https://graph.qq.com/oauth2.0/token";
    const GET_OPENID_URL = "https://graph.qq.com/oauth2.0/me";

    public $appid;
    public $appkey;
    public $appcallback;
    public $apperrreport;
    public $appscope;
    
    public $callback_code;
    
    function __construct(){
        $this->appid = QQ_APPID;
        $this->appkey = QQ_APPKEY;
        $this->appcallback = QQ_CALLBACK;
        $this->apperrreport = QQ_ERRORREPORT;
        $this->appscope = QQ_SCOPE;
        
        if(!$this->appid || !$this->appkey || !$this->appcallback)
            $this->showError('20001');
        
        $this->callback_state = $_GET['state'];
    }

    public function qq_login(){
        
        //-------生成唯一随机串防CSRF攻击
        $state = md5(uniqid(rand(), true));
        
        $this->qq_static_var('state',$state);

        //-------构造请求参数列表
        $keysArr = array(
            "response_type" => "code",
            "client_id" => $this->appid,
            "redirect_uri" => urlencode($this->appcallback),
            "state" => $state,
            "scope" => $this->appscope,
        );

        $login_url = $this->combineURL(self::GET_AUTH_CODE_URL, $keysArr);
        
        // 直接跳走
        header("Location:$login_url");
        exit();
    }

    public function qq_callback(){
        $state = $this->qq_static_var('state');
        $code  = $_REQUEST['code'];
        
        //--------验证state防止CSRF攻击
        if($this->callback_state !== $state){
            $this->showError("30001");
        }

        //-------请求参数列表
        $keysArr = array(
            "grant_type" => "authorization_code",
            "client_id" => $this->appid,
            "redirect_uri" => urlencode($this->appcallback),
            "client_secret" => $this->appkey,
            "code" =>$code,
        );

        //------构造请求access_token的url
        $token_url = $this->combineURL(self::GET_ACCESS_TOKEN_URL, $keysArr);
        $response = $this->get_contents($token_url);
        
        if(strpos($response, "callback") !== false){
            $lpos = strpos($response, "(");
            $rpos = strrpos($response, ")");
            $response  = substr($response, $lpos + 1, $rpos - $lpos -1);
            $msg = json_decode($response);

            if(isset($msg->error)){
                $this->showError($msg->error, $msg->error_description);
            }
        }
        
        $params = array();
        parse_str($response, $params);
        return $params["access_token"];
    }

    public function qq_openid($token){
        
        if(!$token)
            return false;
        
        //-------请求参数列表
        $keysArr = array(
            "access_token" => $token
        );

        $graph_url = $this->combineURL(self::GET_OPENID_URL, $keysArr);
        $response = $this->get_contents($graph_url);

        //--------检测错误是否发生
        if(strpos($response, "callback") !== false){
            $lpos = strpos($response, "(");
            $rpos = strrpos($response, ")");
            $response = substr($response, $lpos + 1, $rpos - $lpos -1);
        }

        $user = json_decode($response);
        
        if(isset($user->error)){
            $this->showError($user->error, $user->error_description);
        }
        return $user->openid;
    }
    
    public function qq_static_var($key, $val=null){
        static $i =0;
        
        if($i === 0 && !$_SESSION) // 暂时用session.
            session_start();
        
        // 只有一个参数时是读, 否则是写.
        if($val !== null){
           $_SESSION[$key] = $val; 
        }
        
        //无论如何都会返回!
        return $_SESSION[$key];
    }
    
    private function showError($code, $description = '$'){
        $this->errorMsg = array(
            '20001'=>'请将配置完全检查一遍',
            '30001' => 'state防止CSRF攻击, 验证未通过',
            '50001' => '请尝试开启curl支持,重启web服务器',
            );
        
        if(!$this->apperrreport){
            die();//die quietly
        }
    
        echo "<meta charset=\"UTF-8\">";
        if($description == "$"){
            die('<h2>'.$this->errorMsg[$code].'</h2>');
        }else{
            echo "<h3>error:</h3>$code";
            echo "<h3>msg  :</h3>$description";
        }
        exit(); 
    }
    
    private function combineURL($baseURL,$keysArr){
        $combined = $baseURL.'?';
        $valueArr = array();

        foreach($keysArr as $key => $val){
            if($val)
            $valueArr[] = "$key=$val";
        }

        $keyStr = implode("&",$valueArr);
        $combined .= ($keyStr);
        
        return $combined;
    }
    private function get_contents($url){
        if (!function_exists('curl_init')) {
            $response = file_get_contents($url);
        }else{
            $ch = curl_init();
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
            curl_setopt($ch, CURLOPT_URL, $url);
            $response =  curl_exec($ch);
            curl_close($ch);
        }

        //-------请求为空
        if(empty($response)){
            $this->showError("50001");
        }

        return $response;
    }
}



<?php
    define('QQ_APPID','');
    define('QQ_APPKEY','');
    define('QQ_CALLBACK','http://www.os688.com/test.php?call=1'); //返回相同的url, 可以用参数.
    define('QQ_ERRORREPORT',true); // 是否调试.
    define('QQ_SCOPE',null); // 授权哪些项, 通常默认即可.
    
    include 'Oauth.class.php';
    $t = new Oauth();
    
    echo '<pre>';
    if($_GET['call']){
        $token = $t->qq_callback();
        echo 'token:'. $token."\n";
        $openid = $t->qq_openid($token);
        echo 'openid:'.$openid;
    }else{
        $t->qq_login();
    }
?>


调用示例, 一个类库即可解决QQ登录的问题, 配置也更加明了.

END





你可能感兴趣的:(QQ登录互联SDK详解 & 优化.)