用QQ登录第三方网站几乎成为了必不可少的功能,用起来也确实很方便。下面介绍一下如何为网站添加QQ登录功能。
成为开发者
具体操作详情:
http://wiki.connect.qq.com/%E6%88%90%E4%B8%BA%E5%BC%80%E5%8F%91%E8%80%85
创建应用
开发者身份审核通过后,就可以创建应用了,操作详情:
https://wiki.connect.qq.com/__trashed-2
下图是我创建一个网站应用填写信息的示例:
原理介绍
首先调用QQ提供的授权接口,这一步会要求用户登录QQ并进行授权,授权成功后,QQ服务器会携带code值返回开发者的回调地址,然后通过code获取access_token,再通过access_token获取用户的openid,这里可通过openid判断用户是否存在,如果存在可进行登录成功的逻辑,不存在,则通过openid获取用户信息,存入数据库。
编写代码
如果从官网下载下来PHP SDK的话,我们可以看一下代码,不是很复杂,但是要把SDK引入框架,需要做一些修改。由于这篇文章主要是介绍QQ登录的原理,因此我把SDK的几个类的代码进行了整合,把主要用到的方法放在了Api模块下的Qqlogin控制器,这样方便查看各个方法,而不用各个类之间来回切换。
假设前台有个QQ登录的按钮,跳转到Home模块下的Qqlogin控制器qq_login方法。
Home模块Qqlogin控制器:
<?php
namespace Home\Controller;
use Api\Controller\QqloginController as Qqloginapi;
use Vendor\Page;
class QqloginController extends ComController
{
public function qq_login()
{
$Oauth = new Qqloginapi();
//第一步:授权并获取code
$Oauth -> qq_login();
}
//回调
public function callback()
{
$Oauth = new Qqloginapi();
//第二步:通过code获取access_token
$access_token = $Oauth -> qq_callback();
//第三步:通过access_token获取登录者的openid
$openid = $Oauth -> get_openid();
//此处为演示,不建议直接通过openid验证登录,应进行第四步
$exist = M('user') -> where(['openid' => $openid]) -> getField('id');
if($exist){
session('uid',$exist);
$this -> redirect('Home/Index/index');
}else{
// 第四步:通过openid获取用户信息
$user_info = $Oauth -> get_user_info();
// dump($userinfo);
if($user_info['gender'] == '男'){
$data['gender'] = 1;
}elseif($user_info['gender'] == '女'){
$data['gender'] = 2;
}else{
$data['gender'] = 3;
}
$data['nickname'] = $user_info['nickname'];
$data['avatar'] = $user_info['figureurl_1'];
$data['addtime'] = time();
$data['openid'] = $openid;
if($user_info['province']){
$data['province'] = $user_info['province'];
}
if($user_info['city']){
$data['city'] = $user_info['city'];
}
$res = M('user') -> data($data) -> add();
if($res){
session('uid',$res);
$this -> redirect('Home/Index/index');
}
}
}
}
Api模块Qqlogin:
<?php
namespace Api\Controller;
use Vendor\Page;
class QqloginController extends ComController
{
const GET_AUTH_CODE_URL = "https://graph.qq.com/oauth2.0/authorize"; //登录页面授权,获取code
const GET_ACCESS_TOKEN_URL = "https://graph.qq.com/oauth2.0/token"; //获取Token
const GET_OPENID_URL = "https://graph.qq.com/oauth2.0/me"; //获得用户OpenId
private $APIMap = array(
/* qzone */
"add_blog" => array(
"https://graph.qq.com/blog/add_one_blog",
array("title", "format" => "json", "content" => null),
"POST"
),
"add_topic" => array(
"https://graph.qq.com/shuoshuo/add_topic",
array("richtype","richval","con","#lbs_nm","#lbs_x","#lbs_y","format" => "json", "#third_source"),
"POST"
),
"get_user_info" => array(
"https://graph.qq.com/user/get_user_info",
array("format" => "json"),
"GET"
),
"add_one_blog" => array(
"https://graph.qq.com/blog/add_one_blog",
array("title", "content", "format" => "json"),
"GET"
),
"add_album" => array(
"https://graph.qq.com/photo/add_album",
array("albumname", "#albumdesc", "#priv", "format" => "json"),
"POST"
),
"upload_pic" => array(
"https://graph.qq.com/photo/upload_pic",
array("picture", "#photodesc", "#title", "#albumid", "#mobile", "#x", "#y", "#needfeed", "#successnum", "#picnum", "format" => "json"),
"POST"
),
"list_album" => array(
"https://graph.qq.com/photo/list_album",
array("format" => "json")
),
"add_share" => array(
"https://graph.qq.com/share/add_share",
array("title", "url", "#comment","#summary","#images","format" => "json","#type","#playurl","#nswb","site","fromurl"),
"POST"
),
"check_page_fans" => array(
"https://graph.qq.com/user/check_page_fans",
array("page_id" => "314416946","format" => "json")
),
/* wblog */
"add_t" => array(
"https://graph.qq.com/t/add_t",
array("format" => "json", "content","#clientip","#longitude","#compatibleflag"),
"POST"
),
"add_pic_t" => array(
"https://graph.qq.com/t/add_pic_t",
array("content", "pic", "format" => "json", "#clientip", "#longitude", "#latitude", "#syncflag", "#compatiblefalg"),
"POST"
),
"del_t" => array(
"https://graph.qq.com/t/del_t",
array("id", "format" => "json"),
"POST"
),
"get_repost_list" => array(
"https://graph.qq.com/t/get_repost_list",
array("flag", "rootid", "pageflag", "pagetime", "reqnum", "twitterid", "format" => "json")
),
"get_info" => array(
"https://graph.qq.com/user/get_info",
array("format" => "json")
),
"get_other_info" => array(
"https://graph.qq.com/user/get_other_info",
array("format" => "json", "#name", "fopenid")
),
"get_fanslist" => array(
"https://graph.qq.com/relation/get_fanslist",
array("format" => "json", "reqnum", "startindex", "#mode", "#install", "#sex")
),
"get_idollist" => array(
"https://graph.qq.com/relation/get_idollist",
array("format" => "json", "reqnum", "startindex", "#mode", "#install")
),
"add_idol" => array(
"https://graph.qq.com/relation/add_idol",
array("format" => "json", "#name-1", "#fopenids-1"),
"POST"
),
"del_idol" => array(
"https://graph.qq.com/relation/del_idol",
array("format" => "json", "#name-1", "#fopenid-1"),
"POST"
),
/* pay */
"get_tenpay_addr" => array(
"https://graph.qq.com/cft_info/get_tenpay_addr",
array("ver" => 1,"limit" => 5,"offset" => 0,"format" => "json")
)
);
private $keysArr;
public function _initialize()
{
parent::_initialize();
if(session('openid')){
$this->keysArr = array(
"oauth_consumer_key" => C('QQ_APPID'),
"access_token" => session('access_token'),
"openid" => session('openid')
);
}else{
$this->keysArr = array(
"oauth_consumer_key" => C('QQ_APPID')
);
}
}
public function qq_login(){
//-------生成唯一随机串防CSRF攻击
$state = md5(uniqid(rand(), TRUE));
session('state',$state);
//-------构造请求参数列表
$keysArr = array(
"response_type" => "code",
"client_id" => C('QQ_APPID'),
"redirect_uri" => C('QQ_CALLBACK'),
"state" => session('state'),
"scope" => C('QQ_SCOPE')
);
$login_url = $this->combineURL(self::GET_AUTH_CODE_URL, $keysArr);
header("Location:$login_url");
}
public function qq_callback(){
$state = session('state');
//--------验证state防止CSRF攻击
if(!$state || $_GET['state'] != $state){
return false;
}
//-------请求参数列表
$keysArr = array(
"grant_type" => "authorization_code",
"client_id" => C('QQ_APPID'),
"redirect_uri" => C('QQ_CALLBACK'),
"client_secret" => C('QQ_APPKEY'),
"code" => $_GET['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);
session('access_token',$params["access_token"]);
return $params["access_token"];
}
public function get_openid(){
//-------请求参数列表
$keysArr = array(
"access_token" => session('access_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);
}
//------记录openid
session('openid',$user -> openid);
return $user->openid;
}
/**
* showError
* 显示错误信息
* @param int $code 错误代码
* @param string $description 描述信息(可选)
*/
public function showError($code, $description = '
){
echo "<meta charset=\"UTF-8\">";
echo "<h3>error:</h3>$code";
echo "<h3>msg :</h3>$description";
exit();
}
//调用相应api
private function _applyAPI($arr, $argsList, $baseUrl, $method){
$pre = "#";
$keysArr = $this->keysArr;
$optionArgList = array();//一些多项选填参数必选一的情形
foreach($argsList as $key => $val){
$tmpKey = $key;
$tmpVal = $val;
if(!is_string($key)){
$tmpKey = $val;
if(strpos($val,$pre) === 0){
$tmpVal = $pre;
$tmpKey = substr($tmpKey,1);
if(preg_match("/-(\d$)/", $tmpKey, $res)){
$tmpKey = str_replace($res[0], "", $tmpKey);
$optionArgList[$res[1]][] = $tmpKey;
}
}else{
$tmpVal = null;
}
}
//-----如果没有设置相应的参数
if(!isset($arr[$tmpKey]) || $arr[$tmpKey] === ""){
if($tmpVal == $pre){//则使用默认的值
continue;
}else if($tmpVal){
$arr[$tmpKey] = $tmpVal;
}else{
if($v = $_FILES[$tmpKey]){
$filename = dirname($v['tmp_name'])."/".$v['name'];
move_uploaded_file($v['tmp_name'], $filename);
$arr[$tmpKey] = "@$filename";
}else{
$this->showError("api调用参数错误","未传入参数$tmpKey");
}
}
}
$keysArr[$tmpKey] = $arr[$tmpKey];
}
//检查选填参数必填一的情形
foreach($optionArgList as $val){
$n = 0;
foreach($val as $v){
if(in_array($v, array_keys($keysArr))){
$n ++;
}
}
if(! $n){
$str = implode(",",$val);
$this->showError("api调用参数错误",$str."必填一个");
}
}
if($method == "POST"){
if($baseUrl == "https://graph.qq.com/blog/add_one_blog") $response = $this->urlUtils->post($baseUrl, $keysArr, 1);
else $response = $this->post($baseUrl, $keysArr, 0);
}else if($method == "GET"){
$response = $this->get($baseUrl, $keysArr);
}
return $response;
}
/**
* _call
* 魔术方法,做api调用转发
* @param string $name 调用的方法名称
* @param array $arg 参数列表数组
* @since 5.0
* @return array 返加调用结果数组
*/
public function __call($name,$arg){
//如果APIMap不存在相应的api
if(empty($this->APIMap[$name])){
$this->showError("api调用名称错误","不存在的API: <span style='color:red;'>$name</span>");
}
//从APIMap获取api相应参数
$baseUrl = $this->APIMap[$name][0];
$argsList = $this->APIMap[$name][1];
$method = isset($this->APIMap[$name][2]) ? $this->APIMap[$name][2] : "GET";
if(empty($arg)){
$arg[0] = null;
}
//对于get_tenpay_addr,特殊处理,php json_decode对\xA312此类字符支持不好
if($name != "get_tenpay_addr"){
$response = json_decode($this->_applyAPI($arg[0], $argsList, $baseUrl, $method));
$responseArr = $this->objToArr($response);
}else{
$responseArr = $this->simple_json_parser($this->_applyAPI($arg[0], $argsList, $baseUrl, $method));
}
//检查返回ret判断api是否成功调用
if($responseArr['ret'] == 0){
return $responseArr;
}else{
$this->showError($response->ret, $response->msg);
}
}
//php 对象到数组转换
private function objToArr($obj){
if(!is_object($obj) && !is_array($obj)) {
return $obj;
}
$arr = array();
foreach($obj as $k => $v){
$arr[$k] = $this->objToArr($v);
}
return $arr;
}
/**
* get
* get方式请求资源
* @param string $url 基于的baseUrl
* @param array $keysArr 参数列表数组
* @return string 返回的资源内容
*/
public function get($url, $keysArr){
$combined = $this->combineURL($url, $keysArr);
return $this->get_contents($combined);
}
/**
* post
* post方式请求资源
* @param string $url 基于的baseUrl
* @param array $keysArr 请求的参数列表
* @param int $flag 标志位
* @return string 返回的资源内容
*/
public function post($url, $keysArr, $flag = 0){
$ch = curl_init();
if(! $flag) curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($ch, CURLOPT_POST, TRUE);
curl_setopt($ch, CURLOPT_POSTFIELDS, $keysArr);
curl_setopt($ch, CURLOPT_URL, $url);
$ret = curl_exec($ch);
curl_close($ch);
return $ret;
}
//简单实现json到php数组转换功能
private function simple_json_parser($json){
$json = str_replace("{","",str_replace("}","", $json));
$jsonValue = explode(",", $json);
$arr = array();
foreach($jsonValue as $v){
$jValue = explode(":", $v);
$arr[str_replace('"',"", $jValue[0])] = (str_replace('"', "", $jValue[1]));
}
return $arr;
}
/**
* combineURL
* 拼接url
* @param string $baseURL 基于的url
* @param array $keysArr 参数列表数组
* @return string 返回拼接的url
*/
public function combineURL($baseURL,$keysArr){
$combined = $baseURL."?";
$valueArr = array();
foreach($keysArr as $key => $val){
$valueArr[] = "$key=$val";
}
$keyStr = implode("&",$valueArr);
$combined .= ($keyStr);
return $combined;
}
public function get_contents($url){
if (ini_get("allow_url_fopen") == "1") {
$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)){
return false;
}
return $response;
}
}
config.php:
<?php
return array(//'配置项'=>'配置值'
//QQ登录配置项
'QQ_APPID' => '应用的appid',
'QQ_APPKEY' => '应用的appkey',
'QQ_CALLBACK' => 'https://example.acier.cn/index.php/Home/Qqlogin/callback', //回调地址
'QQ_SCOPE' => 'get_user_info,list_album,add_album,upload_pic,add_topic,add_weibo', //授权接口列表
);
原创文章转载请注明:转载自:第三方登录(一)QQ登录
发表评论
沙发空缺中,还不快抢~