文章目录
引言
我们先拿腾讯自带的php-sdk来看看:
腾讯官方SDK下载
主要的代码都在API/class/Oauth.class.php中,内容总共就三个函数:
public function qq_login(){
$appid = $this->recorder->readInc("appid");
$callback = $this->recorder->readInc("callback");
$scope = $this->recorder->readInc("scope");
//-------生成唯一随机串防CSRF攻击
$state = md5(uniqid(rand(), TRUE));
$this->recorder->write('state',$state);
//-------构造请求参数列表
$keysArr = array(
"response_type" => "code",
"client_id" => $appid,
"redirect_uri" => $callback,
"state" => $state,
"scope" => $scope
);
$login_url = $this->urlUtils->combineURL(self::GET_AUTH_CODE_URL, $keysArr);
header("Location:$login_url");
}
public function qq_callback(){
$state = $this->recorder->read("state");
//--------验证state防止CSRF攻击
if(!$state || $_GET['state'] != $state){
$this->error->showError("30001");
}
//-------请求参数列表
$keysArr = array(
"grant_type" => "authorization_code",
"client_id" => $this->recorder->readInc("appid"),
"redirect_uri" => urlencode($this->recorder->readInc("callback")),
"client_secret" => $this->recorder->readInc("appkey"),
"code" => $_GET['code']
);
//------构造请求access_token的url
$token_url = $this->urlUtils->combineURL(self::GET_ACCESS_TOKEN_URL, $keysArr);
$response = $this->urlUtils->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->error->showError($msg->error, $msg->error_description);
}
}
$params = array();
parse_str($response, $params);
$this->recorder->write("access_token", $params["access_token"]);
return $params["access_token"];
}
public function get_openid(){
//-------请求参数列表
$keysArr = array(
"access_token" => $this->recorder->read("access_token")
);
$graph_url = $this->urlUtils->combineURL(self::GET_OPENID_URL, $keysArr);
$response = $this->urlUtils->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->error->showError($user->error, $user->error_description);
}
//------记录openid
$this->recorder->write("openid", $user->openid);
return $user->openid;
}
简单看一下有几个函数:$this->recorder->readInc、$this->recorder->write、$this->urlUtils->combineURL、$this->urlUtils->get_contents、$this->error->showError
共计92行代码,然后再看看recorder的readInc和write,emmmmm结果recorder是个session对象操作类和配置文件读取类。操作结果都写到session里emmmmm。试着用vendor调用发现一堆bug根本用不了。。。所以只能自己根据API文档摸索了。
Vendor文件配置
先说最终需要配置的文件数量:2个。
其中SDK文件一个Controller文件一个。
首先先确定我们拥有的内容:
[
'appid'=>"你的APP_ID",
'callback'=>"你的回调地址",
'appkey'=>"你的APP_KEY"
]
这些自然就是我们的配置文件的构成。我们可以将这部分放到业务逻辑的配置中。
然后就是调用流程了。
1.基础接口访问能力
腾讯QQ互联SDK的URL.class.php的四个方法都很不错,可以拿来改改使用,修改后的静态方法如下:
public static function do_post($url, $data){
$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($ch, CURLOPT_POST, TRUE);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_URL, $url);
$ret = curl_exec($ch);
curl_close($ch);
return $ret;
}
public static function get_url_contents($url){
if (ini_get("allow_url_fopen") == "1")
return file_get_contents($url);
$ch = curl_init();
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($ch, CURLOPT_URL, $url);
$result = curl_exec($ch);
curl_close($ch);
return $result;
}
public static function combineURL($baseURL,$keysArr){
$combined = $baseURL."?";
$valueArr = [];
foreach($keysArr as $key => $val){
$valueArr[] = "$key=$val";
}
$keyStr = implode("&",$valueArr);
$combined .= ($keyStr);
return $combined;
}
拿来可以直接self调用,很舒服
API接口也可以拿来用:
public static $version = "2.0";
public static $getAuthCodeUrl = "https://graph.qq.com/oauth2.0/authorize";
public static $getAccessTokenUrl = "https://graph.qq.com/oauth2.0/token";
public static $getOpenIDUrl = "https://graph.qq.com/oauth2.0/me";
public static $getUserInfoUrl = "https://graph.qq.com/user/get_user_info";
变量名已经把功能写得很明白了不多说什么了233333
2.拿access_token
腾讯官方WIKI
根据官方文档,这个token需要通过Authorization Code来拿(就是用户授权您拿access_token),所以需要调用一次getAuthCode方法:
public function getAuthCode(){
$appid = $this->config["appid"];
$callback = $this->config["callback"];
$scope = "get_user_info";
//-------生成唯一随机串防CSRF攻击
$state = md5(uniqid(rand(), TRUE));
$this->setSession("state",$state);
//-------构造请求参数列表
$keysArr = [
"response_type" => "code",
"client_id" => $appid,
"redirect_uri" => $callback,
"state" => $state,
"scope" => $scope
];
$login_url = self::combineURL(self::$getAuthCodeUrl, $keysArr);
return $login_url;
}
这个方法返回的是一个链接,这个链接会跳转到用户QQ登录页面,callback就是我们的回调地址。这里的config就是前文所说的配置数组啦,这个数组可以在new的时候新建,也可以直接写成固定值的静态变量,当然也可以写成常量。
setSession是我写的一个写session的方法,配套getSession:
public function setSession($key,$val){
if(!array_key_exists('QQOauth', $_SESSION))
$_SESSION['QQOauth']=[];
$_SESSION['QQOauth'][$key]=$val;
}
public function getSession($key){
if(!array_key_exists('QQOauth', $_SESSION))
$_SESSION['QQOauth']=[];
if(array_key_exists($key, $_SESSION['QQOauth']))
return $_SESSION['QQOauth'][$key];
return false;
}
这个session用来存储随机串防止csrf。
拿到authCode就可以拿access_token了:
public function getAccessToken($code,$state){
$states = $this->getSession("state");
//--------验证state防止CSRF攻击
if(!$states || $states != $state){
return false;
}
//-------请求参数列表
$keysArr = [
"grant_type" => "authorization_code",
"client_id" => $this->config["appid"],
"redirect_uri" => urlencode($this->config["callback"]),
"client_secret" => $this->config["appkey"],
"code" => $code
];
//------构造请求access_token的url
$token_url = self::combineURL(self::$getAccessTokenUrl, $keysArr);
$response = self::get_url_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)){
return false;
}
}
$params = [];
parse_str($response, $params);
return $params["access_token"];
}
这里两个参数code为刚刚拿的authCode,state为url的参数,可以从控制器里拿。
3.拿用户openid
有了access_token之后拿用户open_id等信息就很容易了:
public function getOpenID($access_token){
//-------请求参数列表
$keysArr = ["access_token" => $access_token];
$graph_url = self::combineURL(self::$getOpenIDUrl, $keysArr);
$response = self::get_url_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)){
return false;
}
return $user->openid;
}
4.获取用户信息
public function getUserInfo($access_token,$openid){
//-------请求参数列表
$appid = $this->config["appid"];
$keysArr = [
"access_token" => $access_token,
"oauth_consumer_key" => $appid,
"openid" => $openid
];
$graph_url = self::combineURL(self::$getUserInfoUrl, $keysArr);
$response = self::get_url_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)){
return false;
}
return $user;
}
5.总结起来的代码:
<?php
class QQ{
public static $version = "2.0";
public static $getAuthCodeUrl = "https://graph.qq.com/oauth2.0/authorize";
public static $getAccessTokenUrl = "https://graph.qq.com/oauth2.0/token";
public static $getOpenIDUrl = "https://graph.qq.com/oauth2.0/me";
public static $getUserInfoUrl = "https://graph.qq.com/user/get_user_info";
public static function do_post($url, $data){
$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($ch, CURLOPT_POST, TRUE);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_URL, $url);
$ret = curl_exec($ch);
curl_close($ch);
return $ret;
}
public static function get_url_contents($url){
if (ini_get("allow_url_fopen") == "1")
return file_get_contents($url);
$ch = curl_init();
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($ch, CURLOPT_URL, $url);
$result = curl_exec($ch);
curl_close($ch);
return $result;
}
public static function combineURL($baseURL,$keysArr){
$combined = $baseURL."?";
$valueArr = [];
foreach($keysArr as $key => $val){
$valueArr[] = "$key=$val";
}
$keyStr = implode("&",$valueArr);
$combined .= ($keyStr);
return $combined;
}
public $config;
public function setSession($key,$val){
if(!array_key_exists('QQOauth', $_SESSION))
$_SESSION['QQOauth']=[];
$_SESSION['QQOauth'][$key]=$val;
}
public function getSession($key){
if(!array_key_exists('QQOauth', $_SESSION))
$_SESSION['QQOauth']=[];
if(array_key_exists($key, $_SESSION['QQOauth']))
return $_SESSION['QQOauth'][$key];
return false;
}
public function __construct($config){
$this->config=$config;
}
public function getAuthCode(){
$appid = $this->config["appid"];
$callback = $this->config["callback"];
$scope = "get_user_info";
//-------生成唯一随机串防CSRF攻击
$state = md5(uniqid(rand(), TRUE));
$this->setSession("state",$state);
//-------构造请求参数列表
$keysArr = [
"response_type" => "code",
"client_id" => $appid,
"redirect_uri" => $callback,
"state" => $state,
"scope" => $scope
];
$login_url = self::combineURL(self::$getAuthCodeUrl, $keysArr);
return $login_url;
}
public function getAccessToken($code,$state){
$states = $this->getSession("state");
//--------验证state防止CSRF攻击
if(!$states || $states != $state){
return false;
}
//-------请求参数列表
$keysArr = [
"grant_type" => "authorization_code",
"client_id" => $this->config["appid"],
"redirect_uri" => urlencode($this->config["callback"]),
"client_secret" => $this->config["appkey"],
"code" => $code
];
//------构造请求access_token的url
$token_url = self::combineURL(self::$getAccessTokenUrl, $keysArr);
$response = self::get_url_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)){
return false;
}
}
$params = [];
parse_str($response, $params);
return $params["access_token"];
}
public function getOpenID($access_token){
//-------请求参数列表
$keysArr = ["access_token" => $access_token];
$graph_url = self::combineURL(self::$getOpenIDUrl, $keysArr);
$response = self::get_url_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)){
return false;
}
return $user->openid;
}
public function getUserInfo($access_token,$openid){
//-------请求参数列表
$appid = $this->config["appid"];
$keysArr = [
"access_token" => $access_token,
"oauth_consumer_key" => $appid,
"openid" => $openid
];
$graph_url = self::combineURL(self::$getUserInfoUrl, $keysArr);
$response = self::get_url_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)){
return false;
}
return $user;
}
}
Controller的配置
我将上一步用于Vendor的QQ.php存放于vendor的qqconnect文件夹下。故此我初始化QQ类的方法如下:
private $qq;
private $qqConfig=[
'appid'=>"您的appid",
'callback'=>"您的回调地址",
'appkey'=>"您的appkey"
];
function __construct(){
parent::__construct();
vendor("qqconnect.QQ");
$qqConfig = $this->qqConfig;
$qqConfig['callback']=url("user/login/qqlogin",false,true,true);
$this->qq = new \QQ($qqConfig);
}
1.跳转到登录授权页
直接getAuthCode拿到链接后header跳转就好了
public function qqLogin(){
$url = $this->qq->getAuthCode();
header("Location:".$url);
exit();
}
2.授权回调
拿到回调的code和state(一般不授权的直接关网页了,所以这两个参数一般都是有的)code参数即为authCode,state为防csrf的随机串。
function notify(){
vendor("qqconnect.QQ");
$param = $this->request->param();
$code = $param['code'];
$state = $param['state'];
$qqConfig = $this->qqConfig;
$qqConfig['callback']=url("你的控制器链接,可以直接拿$code['callback'],回调与你当前链接必须一致。",false,true,true);
$qq = new \QQ($qqConfig);
$accessToken = $qq->getAccessToken($code,$state);
if(!$accessToken)
$this->error("非法操作");
$openid = $qq->getOpenID($accessToken);
}
拿到openid就可以拿openid做唯一字段进行登录了。
3.获取用户头像昵称信息
$tencent_userInfo=$qq->getUserInfo($accessToken,$openid);
$userInfo = json_decode(json_encode($tencent_userInfo),true);
你可以把userInfo写到一个session中在绑定用户时随时调用。
$avatar = $userInfo['figureurl_qq_1'];//头像
$nickname = $userInfo['nickname'];//用户昵称