本文,基于 Laravel 框架的内嵌语法,进行编码,原生实现也是一样,只是接收参数,读配置文件的语法变了。
在讲解支付宝、QQ、微博、Github、百度账号等平台的第三方登录之前,我们有必要先了解一下,第三方登录所使用的协议。具体的详细内容请看我的这篇博客:
https://blog.csdn.net/weixin_43885417/article/details/91163338
先奉上第三方登录原理图
开始入正题,既然知道了授权协议,下一步,肯定开始申请开发者身份,获取请求接口的权限。
先来一个简单的热热身:
一 、Github
详细步骤:
- 首先进入 Github 官网登录
https://github.com
等你注册好之后,你就可以看到上上一个图片中显示你的应用,点进去
然后开始进入开发环节:
在看我的代码之前,可以看看官方文档的接口和参数要求:
https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/ - 首先我们需要在前端页面中添加一个登录连接,类似这样:
https://github.com/login/oauth/authorize?client_id=你自己 APPID&redirect_uri =你的回调地址&scope =user&state=你自己的识别字段值(github 那边会再次返回给你,让你判断是不是你想要的请求)。
github 会回调你的接口地址,类似这样。
https://blogback.zhangtengfei-steven.cn/gitHub?code=425e65d6c1e18c821683&state=上面提到的标识
你需要用 PHP 自带的$_GET[‘code’],获取 code,用它去获取 access_token。 - 请求https://github.com/login/oauth/access_token,获取access_token。需要传 client_id, client_secret, code。
github那边会以 URL 参数格式返回给你结果,类似这样:
access_token=d14bec07c422b4d03b11b9954f383a292ee3dd0e&scope=user&token_type=bearer - 拿到 access_token之后,就可以获取用户信息了。
请求https://api.github.com/user
注意!注意!注意!:
这里有一个坑,请求获取用户接口的时候,需要带上 header 头信息,否则会报一个错误,
$headers[] = 'Authorization: token '. (拼接上获取的 access_token);
下面我上菜了:
<?php
namespace App\Http\Controllers\CommonControllers;
use App\Http\Controllers\Controller;//laravel 框架封装好的Controller
use Illuminate\Http\Request; //laravel 框架封装好的 Request
class GitHubLogin extends Controller
{
public function gitHubCallBack(Request $request)
{
if ($request->has('code')) {
$github_login_cg = config('github');//登录的配置文件加载
$param = array(
'code' => $request->code,
'client_id' => $github_login_cg['client_id'],
'client_secret' => $github_login_cg['client_secret'],
);
$url = $github_login_cg['access_token_url'];
$content = getHttpResponsePOST($url, $param); //获取access_token
$data = array();
//$content====>'A=XXXXX&B=XXXX', github以路由参数返回结果
parse_str($content,$data);//解析返回的值,赋给变量$data
//请求用户信息
if (!empty($data['access_token'])) {
$info_url = $github_login_cg['get_user_url'] . $data['access_token'];
$token = $data['access_token'];
$headers[] = 'Authorization: token '. $token;
$headers[] = "User-Agent: 坏小哥博客";
$result = getHttpResponseGET($info_url, $headers);
$user_info = json_decode($result, true);
//接下来就是你自己的业务逻辑
..............
}
}
}
}
上面提到的 github.php配置 文件
<?php
return [
'client_id' => '你自己的client_id',
'client_secret' => '你自己的client_secrect',
'state' => 'xxxxx', //标识字段,防csrf 攻击,github 那边会原模原样返回给你,返回state 不正确,我们不处理
'access_token_url' => 'https://github.com/login/oauth/access_token',//获取toke地址
'get_user_url' => 'https://api.github.com/user?', //获取用户信息地址
];
下面是获取到的信息
二、支付宝:
详细步骤:
- 进入官网,先注册账号,然后申请开发者,进行认证。
https://open.alipay.com/platform/home.htm
然后创建一个应用
进入你的应用之后,你会看到能力列表,添加能力,获取接口权限
我们实现第三方登录只需有获取会员信息和第三方应用授权这俩接口就行。
点击应用信息
上面有APPID,还有上面圈住的都要进行配置,我们调用支付宝的接口,需要传我们的私钥,然后支付宝那边会回调我们配置的回调接口。
接下来就是开发流程:
支付宝获取用户信息的开发文档:https://opendocs.alipay.com/open/284/web - 首先,我们需要前台点击支付宝登录按钮,请求支付宝授权
URL:https://openauth.alipay.com/oauth2/publicAppAuthorize.htm?app_id=APPID&scope=SCOPE&redirect_uri=你的回调地址,地址要和支付宝平台配置一致
之后,支付宝会跳转到你配置的回调地址,类似这样:
http://example.com/doc/toAuthPage.html?app_id=2014101500013658&source=alipay_wallet&scope=auth_user&auth_code=ca34ea491e7146cc87d25fca24c4cD11
你可以通过PHP全局变量$_GET[‘auth_code’],获取auth_code。 - 拿auth_code换access_token
auth_code、app_id(你的应用ID)、scope (接口权限值,目前只支持 auth_user 和 auth_base 两个值)
获取access_token,接口地址:https://opendocs.alipay.com/apis/api_9/alipay.system.oauth.token
在获取access_token的接口中,你要传一个sign(签名)参数,这个参数的值,需要我们自己拼接处理。
下载生成私钥和公钥工具的地址:
https://opendocs.alipay.com/open/291/105971
文档写的很详细,在这里不再叙述,应该在配置应用的时候,已经下过,也进行配置了。把我们的公钥复制到应用信息接口加签方式里,目的是,支付宝会根据你的公钥进行解密你传的加密数据(利用的是非对称加密原理)
具体的sign(签名生成,代码中会解释说明)。
根据文档说明,进行传参,POST请求,不出意外,我们可以得到以json形式返回的access_token信息。 - 获取用户信息,传access_token,POST方式请求用户信息接口。
注意,用户信息数据是以gbk的编码格式返回给你数据,需要你用php的mb_convert_encoding编码转换函数,转换一下。才能解析,否则中文会乱码的。
至此,流程讲解完毕。
开始上菜:
<?php
namespace App\Http\Controllers\CommonControllers;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class AliPayLogin extends Controller
{
public function aliPayLoginCallBack(Request $request)
{
if (!$request->has('auth_code')) {
echo '获取app_auth_code失败,请重试';
exit;
}
//获取app_auth_token
$ali_pay_login_cg = config('alipay')['login'];
$base_param = array(
'app_id' => $ali_pay_login_cg['app_id'],
'grant_type' => $ali_pay_login_cg['grant_type'],
'charset' => $ali_pay_login_cg['charset'],
'sign_type' => $ali_pay_login_cg['sign_type'],
'version' => $ali_pay_login_cg['version'],
);
$get_token_param = array_merge($base_param, array(
'method' => $ali_pay_login_cg['get_token_api'],
'code' => $request->auth_code,
'timestamp' => date('Y-m-d H:i:s')
));
$signStr = aliPayParamToString($get_token_param); //参数拼接
$rsa = enRSA2($signStr); //对拼接好的请求参数数据进行RSA加密
$sign = urlencode($rsa); //生成url格式的请求参数
$query = $signStr . '&sign=' . $sign; //请求参数
$url = $ali_pay_login_cg['base_url'] . $query;
$access_token = getHttpResponsePOST($ali_pay_login_cg['base_url'], $query);
$access_token_info = json_decode($access_token, true);
if (!isset($access_token_info['alipay_system_oauth_token_response'])) {
echo '获取token信息失败';
exit;
}
$access_token = $access_token_info['alipay_system_oauth_token_response']['access_token'];
//请求用户信息
$get_info_param = array_merge($base_param, array(
'method' => $ali_pay_login_cg['user_info_api'],
'timestamp' => date('Y-m-d H:i:s'),
'auth_token' => $access_token,
));
$signStr = aliPayParamToString($get_info_param);//拼接参数
$rsaStr = enRSA2($signStr); //RSA加密
$sign = urlencode($rsaStr); //生成url格式的请求参数
$query = $signStr . '&sign=' . $sign;请求参数
$user_info = getHttpResponsePOST($ali_pay_login_cg['base_url'], $query);
$user_info = mb_convert_encoding($user_info, 'utf-8', 'gbk');//把返回结果,从gbk格式转为utf-8格式
$user_info = json_decode($user_info, true); //解析json
//接下来,写自己的业务逻辑
.........................
}
}
aliPay.php(请求配置文件)
<?php
return [
'login' => [
'app_id' => 'yourselfID', //应用ID
'format' => 'json', //要求支付宝返回的数据格式
'charset' => 'utf-8', //我们的请求参数的编码格式
'sign_type' => 'RSA2', //签名加密方式
'version' => '1.0', //调用接口的版本
'scope' => 'auth_user', //获取的信息类型
'grant_type' => 'authorization_code', //获取code需要传,固定格式
'base_url' => 'https://openapi.alipay.com/gateway.do',//请求支付宝的基础接口地址
'get_token_api' => 'alipay.system.oauth.token', //请求access_token的接口名
'user_info_api' => 'alipay.user.info.share' //请求用户信息的接口名
]
];
上面提到的aliPayParamToString参数拼接方法
function aliPayParamToString($dataArr)
{
ksort($dataArr); //按键进行降序排序,这个必须要做
$signStr = '';
foreach ($dataArr as $key => $val) {
if (empty($signStr)) {
$signStr = $key.'='.$val;
} else {
$signStr .= '&'.$key.'='.$val;
}
}
return $signStr;
}
上面提到的enRSA2参数拼接方法
function enRSA2($data)
{
$path = public_path() . '/key/private_key.txt'; //私钥地址
$private_key = file_get_contents($path); //读取私钥内容
$str = chunk_split(trim($private_key), 64, "\n"); //去除空格,并且每64个字符,加一个换行符。
$key = "-----BEGIN RSA PRIVATE KEY-----\n$str-----END RSA PRIVATE KEY-----\n";
$signature = '';
$signature = openssl_sign($data, $signature, $key, OPENSSL_ALGO_SHA256) ? base64_encode($signature) : NULL;//数据加密
return $signature;
}
获取支付宝用户的信息如下:
插语:
GitHub、QQ、微博、百度账号、支付宝的第三方登录实现大同小异,都是遵循OAuth2.0协议。可能中间返回的参数格式,请求方式有略微的区别。因此,我以 GitHub 为例,除了支付宝,需要特别详细说明,其他的登录方式,我简单说一下注册应用方式,还有开发遇到的坑,之后就直接上代码。
三、百度账号
注册应用的步骤:
- 首先登录百度开发者中心:
https://developer.baidu.com
注册百度账号,创建应用。
创建完后,可以看到应用信息
点击安全设置,配置信息。
- 开始开发,详细步骤可以看文档。
http://developer.baidu.com/wiki/index.php?title=docs/oauth/application
我这里直接上菜了。
<?php
namespace App\Http\Controllers\CommonControllers;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class BaiDuLogin extends Controller
{
public function baiDuCallBack(Request $request)
{
if (! $request->has('code')) {
echo '没有获取到code';
exit;
}
$baidu_login_fg = config('baidu');
$param = array(
'grant_type' => $baidu_login_fg['grant_type'],
'code' => $request->code,
'client_id' => $baidu_login_fg['client_id'],
'client_secret' => $baidu_login_fg['client_secret'],
'redirect_uri' => $baidu_login_fg['redirect_uri'],
);
$url = $baidu_login_fg['access_token_url'];
$token = json_decode(getHttpResponsePOST($url, $param), true);
$access_token = $token['access_token'];
if (!empty($access_token)) {
$url = $baidu_login_fg['user_info_url'] . $access_token;
$info = getHttpResponseGET($url);
//接下来就是你自己的业务逻辑
.........................
}
}
baidu.php 配置文件
<?php
return [
'grant_type' => 'authorization_code', //获取code传递字段
'client_id' => '', //你应用的 ID
'client_secret' => '', //你应用的密钥
'redirect_uri' => '', //你的回调地址
'access_token_url' => 'https://openapi.baidu.com/oauth/2.0/token', //获取token地址
'user_info_url' => 'https://openapi.baidu.com/rest/2.0/passport/users/getInfo?access_token=' //获取信息地址
];
获取到的用户信息如下:
四、微博
- 同样的,进入微博开放平台:
https://open.weibo.com
首先,你要完善信息,申请成为开发者。
然后,创建应用,审核通过后,
进入我的应用页面
会看到基本信息:
进入高级信息页面,需要配置回调地址:
- 微博开发文档:
https://open.weibo.com/wiki/%E6%8E%88%E6%9D%83%E6%9C%BA%E5%88%B6
获取过程不再叙述,直接上菜:
<?php
namespace App\Http\Controllers\CommonControllers;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class WeiBoLogin extends Controller
{
public function weiBoOAuthCallBack(Request $request)
{
if (! $request->has('code')) {
echo '没有获取到code';
exit;
}
$wei_bo_login_cg = config('weibo')['login'];
$param = array(
'client_id' => $wei_bo_login_cg['client_id'],
'client_secret' => $wei_bo_login_cg['client_secret'],
'grant_type' => $wei_bo_login_cg['grant_type'],
'redirect_uri' => $wei_bo_login_cg['oauth_redirect_uri'],
'code' => $request->code
);
$get_token_url = $wei_bo_login_cg['access_token_url'] . http_build_query($param);
$token_info = json_decode(getHttpResponsePOST($get_token_url) ,true);
if (isset($token_info['error'])) {
echo '获取token失败 error_code: ' . $token_info['error_code'] . 'error_description: ' . $token_info['error_description'];
exit;
}
if (empty($token_info['access_token']) || empty($token_info['uid'])) {
echo '获取的token信息为空';
exit;
}
//获取用户信息
$get_user_info_param = array(
'access_token' => $token_info['access_token'],
'uid' => $token_info['uid']
);
$get_user_info_url = $wei_bo_login_cg['user_info_url'] . http_build_query($get_user_info_param);
//接下来就是你自己的业务逻辑
..........................
}
}
weibo.php 配置文件
<?php
return [
'login' => [
'grant_type' => 'authorization_code', //获取code传递字段
'client_id' => '你的应用 ID',
'client_secret' => '你的密钥',
'oauth_redirect_uri' => '你的授权回调地址',
'cancel_oauth_redirect_uri' => '你的取消授权回调地址',
'access_token_url' => 'https://api.weibo.com/oauth2/access_token?', //获取token地址
'user_info_url' => 'https://api.weibo.com/2/users/show.json?' //获取信息地址
],
];
获取的信息(我只展示一部分):
五、QQ
- 首先,进入 QQ 互联,登录后,申请成为开发者,需要审核,再创建应用。
注意!注意!注意!
一定要注意申请信息,否则,你一直无法审核通过。延长你的开发期。 - 官方开发文档:
https://wiki.connect.qq.com/%E5%BC%80%E5%8F%91%E6%94%BB%E7%95%A5_server-side
需要注意,在你获取 access_token时候,返回的错误信息(正常情况下是 普通的 json 数据,没有 callback 包裹)和你获取 openid 正常的数据格式是一样的,这个处理的时候需要注意。
callback( {“client_id”:“YOUR_APPID”,“openid”:“YOUR_OPENID”} );
<?php
namespace App\Http\Controllers\CommonControllers;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class QQLogin extends Controller
{
public function qqCallBack(Request $request)
{
if (! $request->has('code')) {
echo '没有获取到code';
exit;
}
$qq_login_fg = config('qq');
$param = array(
'grant_type' => $qq_login_fg['grant_type'],
'code' => $request->code,
'client_id' => $qq_login_fg['client_id'],
'client_secret' => $qq_login_fg['client_secret'],
'redirect_uri' => $qq_login_fg['redirect_uri'],
);
$token_url = $qq_login_fg['access_token_url'] . http_build_query($param);
$response = file_get_contents($token_url);
if (strpos($response, "callback") !== false)
{
$error_msg = dealQQData($response);
dealQQErrorMessage($error_msg, '获取token信息失败');
exit;
}
$access_token_info = array();
parse_str($response, $access_token_info);
if (empty($access_token_info['access_token'])) {
echo '获取的token信息为空,稍后重试';
exit;
}
$access_token = $access_token_info['access_token'];
$get_openid_url = $qq_login_fg['openid_url'] . $access_token;
$openid_data = getHttpResponseGET($get_openid_url);
if (strpos($openid_data, "callback") === false) {
echo '获取openid异常,稍后重试!';
exit;
}
$openid_data = dealQQData($openid_data);
if (isset($openid_data['error'])) {
dealQQErrorMessage($openid_data, '获取openid失败');
exit;
}
if (empty($openid_data['openid'])) {
echo '获取的openid信息为空,请稍后重试';
exit;
}
$get_info_param = array(
'access_token' => $access_token,
'oauth_consumer_key' => '101849190',
'openid' => $openid_data['openid']
);
$url = $qq_login_fg['user_info_url'] . http_build_query($get_info_param);
$info = json_decode(getHttpResponseGET($url), true);
//接下来是你自己的业务逻辑
.......................
}
}
qq.php 配置文件
<?php
return [
'grant_type' => 'authorization_code', //获取code传递字段
'client_id' => '你自己的 ID',
'client_secret' => '你自己的密钥',
'redirect_uri' => '你自己的回调地址',
'access_token_url' => 'https://graph.qq.com/oauth2.0/token?', //获取token地址
'openid_url' => 'https://graph.qq.com/oauth2.0/me?access_token=',//获取openid地址
'user_info_url' => 'https://graph.qq.com/user/get_user_info?' //获取信息地址
];
开发 QQ 提到的俩公共方法
dealQQData
/**
* 处理QQ返回的数据,返回数组形式
* @param $response
* @return mixed
*/
function dealQQData($response)
{
$left_pos = strpos($response, "(");
$right_pos = strrpos($response, ")");
$response = json_decode(substr($response, $left_pos + 1, $right_pos - $left_pos -1), true);
return $response;
}
dealQQErrorMessage
/**
* 处理QQ登录异常信息
* @param $error_msg
* @param string $msg
*/
function dealQQErrorMessage($error_msg, $msg = '')
{
if (isset($error_msg['error']))
{
echo "<h1>$msg</h1>";
echo "<h3>error:</h3>" . $error_msg['error'];
echo "<h3>msg:</h3>" . $error_msg['error_description'];
}
}
获取 QQ 用户信息如下:
上面提到的请求接口的公共方法:
getHttpResponseGET()
/**
* @param $url
* @param null $header
* @return bool|string
*/
function getHttpResponseGET($url,$header = null) {
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
if(!empty($header)){
curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
}
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($curl);
curl_close($curl);
unset($curl);
return $output;
}
getHttpResponsePOST()
/**
* 远程获取数据,POST模式
* @param string $url
* @param array $param
* @return bool|string
*/
function getHttpResponsePOST($url = '', $param = array()) {
if (empty($url)) {
return false;
}
$ch = curl_init(); //初始化curl
curl_setopt($ch, CURLOPT_URL,$url); //抓取指定网页
curl_setopt($ch, CURLOPT_HEADER, false); //是否返回响应头信息
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); //要求结果为字符串且输出到屏幕上
curl_setopt($ch, CURLOPT_POST, true); //post提交方式
if (!empty($param)) {
curl_setopt($ch, CURLOPT_POSTFIELDS, $param);
}
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); //是否将结果返回
$data = curl_exec($ch); //运行curl
if (curl_errno($ch)) {
echo 'Errno'. json_encode(curl_error($ch)); //捕抓异常
}
curl_close($ch);
return $data;
}