流程:
1)用户同意授权,获取code
2)通过code获取网页授权access_token
3)刷新access_token(如果需要)
4)拉去用户信息(需要scope为snsapi_userinfo)
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;
}
}
?>