PHP開發網站引入第三方登錄之微信登錄、綁定
案例(www.spaceyun.com)
寫在前面的話:
如果在做此項功能之前有去了解OAuth2.0協議,那么接下來一切都很容易理解,如果沒有了解OAuth2.0協議,
也不影響完成此項功能,不過應該是很難領會其原理。
准備工作:
微信登錄時在微信開放平台(open.weixin.qq.com)上面,
注冊登錄之后,*認證開發者資質*(不認證是無法開放網頁登錄授權的),在管理中心添加網站應用,相關信息准備齊全,
通過*審核*之后,可以獲取到APPID和APPSECERT,然后在應用詳情里面查看微信登錄接口狀態,如果為已獲得,表示可以正常開發了。
需要注意的是,網站授權回調域和官網地址是沒有(http)或者(https)的,例如我的網站授權回調域是(www.spaceyun.com)
我的官網地址也是(www.spaceyun.com),
數據表:用戶表里面有openid字段,新建一個微信表,用來存儲微信的openid,nickname,headimgurl,sex,city,province,country等信息
流程:
1)用戶同意授權,獲取code
2)通過code獲取網頁授權access_token
3)刷新access_token(如果需要)
4)拉去用戶信息(需要scope為snsapi_userinfo)
正式開發:
我的開發是在laravel5.2框架里面的,所以代碼會按照框架的模式,不過並沒有使用laravel的擴展包,純手工代碼,
應該有參考價值:
登錄和綁定其實是一套流程,就算綁定,也是操作的snsapi_login的scope接口,就是說,登錄是點擊掃碼獲取openid登錄,
綁定也是點擊掃碼獲取openid綁定。
先以微信登錄為例
路由:
Route::get('wxLogin', 'WeixinController@wxLogin');//點擊微信登錄跳轉到的url路由
Route::get('wxGetCode', 'WeixinController@wxGetCode');//用戶掃碼之后跳轉到的url路由
控制器:
先單個方法分析,后面補上完整代碼。
1.
//login
public function wxLogin(){
$appid = $this->appid;
$redirect_uri = urlencode($this->redirect_uri);
$scope = "snsapi_login";
$state = "wxLogin";
$url = "https://open.weixin.qq.com/connect/qrconnect?appid=".$appid."&redirect_uri=".$redirect_uri."&response_type=code&scope=".$scope."&state=".$state."#wechat_redirect";
return redirect($url);
}
這個方法用來做微信登錄請求觸發,就是用戶點擊微信登錄提示的鏈接是,跳轉到上面的$url,
其中的appid是在開放平台創建應用后獲取的,
redirect_uri是用戶掃碼之后的回調地址,因為會拼接在下面的url里面,需要用urlencode處理,
scope這里必須是"snsapi_login",
state是會提供給回調地址的參數,類似於一個場景值的設定,可以任意設置,用來區分從網站的不同位置發起的請求,建議使用有意義的英文單詞,
url是微信開放平台固定的,里面的參數替換成我們上面的參數,
最后做一個跳轉,我使用的是框架自帶的方法,可根據實際情況更改。
這個操作會出現一個展示微信登錄二維碼的頁面,這個頁面也是微信的頁面,跟我們本地的服務器沒有任何關系。
2.
//reback url-->wxGetCode
public function wxGetCode(){
$code = $_GET['code'];
$state = $_GET['state'];
if($state === 'wxLogin'){
$this->loginWeixin($code);
}elseif($state === 'wxBind'){
$this->bindWeixin($code);
}else{
return redirect("http://www.spaceyun.com");
}
}
這是用戶掃碼之后從微信的二維碼頁面會跳轉到我們服務器的處理頁面,用戶掃碼登錄成功之后,
會攜帶code和state參數提交到我們服務器的服務器的地址,
我們接收code和state,根據state場景值判斷做什么處理,上面我們是wxLogin,會到loginWeixin方法繼續處理code。
3.
//loginWeixin
private function loginWeixin($code){
$appid = $this->appid;
$appsecert = $this->appsecert;
$url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=".$appid."&secret=".$appsecert."&code=".$code."&grant_type=authorization_code";
//curl模擬get請求,獲取結果
$res = $this->http_curl($url);
//轉化為數組
$return_data = json_decode($res,true);
$openid = $return_data['openid'];
//openid存在,直接登錄,openid不存在,先注冊再登錄
$result = User::where('wx_openid',$openid)->first();
if($result){
//login
Auth::loginUsingId($result->id,true);
$jump_url = 'http://www.spaceyun.com/accountSettings';
echo "<script>window.location.href='".$jump_url."'</script>";
return redirect('http://www.spaceyun.com/accountSettings');
}else{
$access_token = $return_data['access_token'];
//獲取用戶基本信息
$getInfoUrl = "https://api.weixin.qq.com/sns/userinfo?access_token=".$access_token."&openid=".$openid."&lang=zh_CN";
$userInfo = $this->http_curl($getInfoUrl);
$weixins = json_decode($userInfo);
//判斷weixins表里面是否存在該條數據
$weixins_result = Weixin::where('openid',$openid)->first();
if(empty($weixins_result)){
//插入數據
Weixin::create([
'openid'=>$weixins->openid,
'nickname'=>$weixins->nickname,
'sex'=>$weixins->sex,
'city'=>$weixins->city,
'province'=>$weixins->province,
'country'=>$weixins->country,
'headimgurl'=>$weixins->headimgurl,
]);
}
//Register and login
$user = new User();
$arr['name'] = $weixins->nickname;
$arr['email'] = $openid."@fake.com";
$arr['password'] = "passwd";
$arr['phone'] = substr($openid,8);
$arr['wx_openid'] = $openid;
$insertId = $user->insertGetId($arr);
Auth::loginUsingId($insertId,true);
$jump_url = 'http://www.spaceyun.com/accountSettings';
echo "<script>window.location.href='".$jump_url."'</script>";
return redirect('http://www.spaceyun.com/accountSettings');
}
}
在這個方法里面,根據appid,appsecert(這兩個都是開放平台獲取的)還有回調返回的code拼接成url,
然后用curl模擬get請求抓取返回數據,我將返回的數據res轉化成數組,獲取到用戶的openid和access_token,
這時在我們的用戶表里面一定要有一個叫做微信的openid的字段,我的字段名是wx_openid,因為我還會有qq_openid和sina_openid,
我們去用戶表查詢,我們獲取到的微信用戶openid在不在我們的用戶表里面,
如果存在,說明用戶是一個老用戶,直接登錄,我用的laravel的Auth::loginUsingId($result->id,true)方法,完成登錄做跳轉,
我跳轉到了賬號設置頁。
如果不存在,說明這是一個新用戶,新用戶需要注冊到用戶表,並且獲取相關信息新增到微信表,
獲取用戶基本信息需要access_token和openid在上面已經獲取到,獲取到信息判斷微信表里面是否存在此條數據,
不存在就插入到微信表,這些數據是微信開放給第三方應用的一些相關信息,包括openid,nickname,headimgurl,
sex,city,province,country等信息,
然后繼續在用戶表里面插入一條數據,因為有一些非空字段,我就自定義生成了一些數據,
最后給這條最新添加的用戶做登錄,登陸之后跳轉。
關於我的跳轉多說一句,因為用這個框架十來天,之前也只開發了一個郵箱相關的功能,所以沒能明白redirect的原理,
准備系統研究laravel時再考慮這個,我要是只用window.location.href或者只用return redirect都無法實現跳轉,
把兩個都寫在這個就可以,應該是有問題的,不過不是錯誤,先這樣吧。
這樣登錄功能就開發完了,綁定功能是相同的流程,只是綁定要在登錄狀態下操作,也不用插入用戶表,只需要改用戶表中wx_openid字段,
需要注意的是一些邏輯上的問題,比如說微信號已經綁定過一個賬號,根據自己應用需求判斷允不允許綁定多個等等。
<?php
namespace App\Http\Controllers;
use App\Weixin;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Http\Requests;
use App\User;
use Cookie;
class WeixinController extends Controller{
//attribute------開放平台獲取
private $appid = "your APPID";
private $appsecert = "your APPSECERT";
//redirect_uri,需要是跳到具體的頁面處理,並且要在開放平台創建應用的授權回調域下面
private $redirect_uri = "http://www.spaceyun.com/wxGetCode";
//login
public function wxLogin(){
$appid = $this->appid;
$redirect_uri = urlencode($this->redirect_uri);
$scope = "snsapi_login";
$state = "wxLogin";
$url = "https://open.weixin.qq.com/connect/qrconnect?appid=".$appid."&redirect_uri=".$redirect_uri."&response_type=code&scope=".$scope."&state=".$state."#wechat_redirect";
return redirect($url);
}
//bind
public function wxBind(){
$appid = $this->appid;
$redirect_uri = urlencode($this->redirect_uri);
$scope = "snsapi_login";
$state = "wxBind";
$url = "https://open.weixin.qq.com/connect/qrconnect?appid=".$appid."&redirect_uri=".$redirect_uri."&response_type=code&scope=".$scope."&state=".$state."#wechat_redirect";
return redirect($url);
}
//reback url-->wxGetCode
public function wxGetCode(){
$code = $_GET['code'];
$state = $_GET['state'];
if($state === 'wxLogin'){
$this->loginWeixin($code);
}elseif($state === 'wxBind'){
$this->bindWeixin($code);
}else{
return redirect("http://www.spaceyun.com");
}
}
//loginWeixin
private function loginWeixin($code){
$appid = $this->appid;
$appsecert = $this->appsecert;
$url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=".$appid."&secret=".$appsecert."&code=".$code."&grant_type=authorization_code";
//curl模擬get請求,獲取結果
$res = $this->http_curl($url);
//轉化為數組
$return_data = json_decode($res,true);
$openid = $return_data['openid'];
//openid存在,直接登錄,openid不存在,先注冊再登錄
$result = User::where('wx_openid',$openid)->first();
if($result){
//login
Auth::loginUsingId($result->id,true);
$jump_url = 'http://www.spaceyun.com/accountSettings';
echo "<script>window.location.href='".$jump_url."'</script>";
return redirect('http://www.spaceyun.com/accountSettings');
}else{
//轉化為數組
$access_token = $return_data['access_token'];
//獲取用戶基本信息
$getInfoUrl = "https://api.weixin.qq.com/sns/userinfo?access_token=".$access_token."&openid=".$openid."&lang=zh_CN";
$userInfo = $this->http_curl($getInfoUrl);
$weixins = json_decode($userInfo);
//判斷weixins表里面是否存在該條數據
$weixins_result = Weixin::where('openid',$openid)->first();
if(empty($weixins_result)){
//插入數據
Weixin::create([
'openid'=>$weixins->openid,
'nickname'=>$weixins->nickname,
'sex'=>$weixins->sex,
'city'=>$weixins->city,
'province'=>$weixins->province,
'country'=>$weixins->country,
'headimgurl'=>$weixins->headimgurl,
]);
}
//Register and login
$user = new User();
$arr['name'] = $weixins->nickname;
$arr['email'] = $openid."@fake.com";
$arr['password'] = "passwd";
$arr['phone'] = substr($openid,8);
$arr['wx_openid'] = $openid;
$insertId = $user->insertGetId($arr);
Auth::loginUsingId($insertId,true);
$jump_url = 'http://www.spaceyun.com/accountSettings';
echo "<script>window.location.href='".$jump_url."'</script>";
return redirect('http://www.spaceyun.com/accountSettings');
}
}
//bindWeixin
private function bindWeixin($code){
$appid = $this->appid;
$appsecert = $this->appsecert;
$url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=".$appid."&secret=".$appsecert."&code=".$code."&grant_type=authorization_code";
//curl模擬get請求,獲取結果
$res = $this->http_curl($url);
//轉化為數組
$return_data = json_decode($res, true);
$access_token = $return_data['access_token'];
$openid = $return_data['openid'];
//openid存在,提示需要解綁其他賬號,openid不存在,先補充信息到weixins表再綁定
$result = User::where('wx_openid',$openid)->first();
if($result){
$jump_url = 'http://www.spaceyun.com/wxBindError';
echo "<script>window.location.href='".$jump_url."'</script>";
return redirect('http://www.spaceyun.com/wxBindError');
}else{
$user_id = Auth::id();
$user = User::find($user_id);
$user->wx_openid = $openid;
$user->save();
//判斷weixins表里面是否存在該條數據
$weixins_result = Weixin::where('openid',$openid)->first();
if(empty($weixins_result)){
//獲取用戶基本信息
$getInfoUrl = "https://api.weixin.qq.com/sns/userinfo?access_token=".$access_token."&openid=".$openid."&lang=zh_CN";
$userInfo = $this->http_curl($getInfoUrl);
$weixins = json_decode($userInfo);
Weixin::create([
'openid'=>$weixins->openid,
'nickname'=>$weixins->nickname,
'sex'=>$weixins->sex,
'city'=>$weixins->city,
'province'=>$weixins->province,
'country'=>$weixins->country,
'headimgurl'=>$weixins->headimgurl,
]);
}
}
$jump_url = 'http://www.spaceyun.com/accountSettings';
echo "<script>window.location.href='".$jump_url."'</script>";
return redirect('http://www.spaceyun.com/accountSettings');
}
//wxBindError
public function wxBindError(){
$wxBindError = '該微信號已被綁定雲享客賬號,如需更改請到之前賬號解除綁定';
return view("weixin.wxBindError",['information'=>$wxBindError]);
}
//解綁
public function wxUnbind(){
//users表里面wx_openid字段清空
$user_id = Auth::id();
$user = User::find($user_id);
if(!preg_match("/^1[34578]{1}\d{9}$/",$user->phone)){
$wxBindError = '請認證手機號之后再解除綁定微信';
return view("weixin.wxBindError",['information'=>$wxBindError]);
}else{
$user->wx_openid = '0';
$user->save();
return redirect('/accountSettings');
}
}
//curl模擬get請求
private function http_curl($url){
$curlobj = curl_init();
curl_setopt($curlobj, CURLOPT_URL, $url);
curl_setopt($curlobj, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curlobj, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($curlobj, CURLOPT_SSL_VERIFYHOST, FALSE);
$output = curl_exec($curlobj);
curl_close($curlobj);
return $output;
}
}
?>