2013-12-26日, 腾讯更新了QQ登录组件2.1, 可以见官方下载链接: QQ click, 重点就是修复一重要的安全问题, 并且在邮件中群发了此消息, 相信许多用户会着手升级与调试. SDK下载后, 就开始表达腾讯的马虎了.
官方md5:1A180F106EF785AE4A04E91EA1882A69
我下载了十多次, 检测md5: D34936AFE7694070EE493B890392C58E
特别是文件md5值, 我们见腾讯的游戏下载, 重要软件都会公布md5值, 防止网络运营商中转离线资源, 可这php sdk我失望了, 下载的包, 跟官方描述的md5是不符合的, 这是描述未更新过来的情况? 对于文件md5值, 个人也把意见提交给QQ旋风, 这是非常重要的. 希望腾讯要认真了.
解压到文件夹后, 发现这版本似乎做了一个demo版本出来, 给普通用户一个直观的界面操作. 如:
这是北大青鸟用户元旦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']; } }
<?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(); } ?>