Thinkphp5实现使用QQ登录

引言

我们先拿腾讯自带的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'];//用户昵称
发布了11 篇原创文章 · 获赞 13 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/similing/article/details/91384876