Dcat Admin默认使用账号密码登录,但我们可以很方便添加微信扫码登录,用户关注公众号后,在登录界面点击微信登录,弹出二维码,用微信扫一扫,如果用户已绑定过账号就自动登录进后台,如果没有绑定账号,则进入绑定账号界面。
原理:前台发起请求=>后台生成带参数二维码并将缓存参数=>前台展示二维码=>用户扫码获取openid并判断是否在数据库中=》前台轮询判断登录成功。
首先安装laravel-wechat
composer require "overtrue/laravel-wechat:^5.1"
生成配置文件:
php artisan vendor:publish --provider="Overtrue\LaravelWeChat\ServiceProvider"
编辑config/wechat.php文件
'official_account' => [
'default' => [
'app_id' => env('WEIXIN_KEY', 'your-app-id'), // AppID
'secret' => env('WEIXIN_SECRET', 'your-app-secret'), // AppSecret
'token' => env('WECHAT_ACCOUNT_TOKEN', 'my_weixin'), // Token
'aes_key' => env('WECHAT_ACCOUNT_AES_KEY', ''), // EncodingAESKey
编辑.env文件,添加微信服务号相关的key
WEIXIN_KEY=wxaf8f
WEIXIN_SECRET=7a00bd3
WEIXIN_REDIRECT_URI="${APP_URL}/weixin/callback"
WECHAT_ACCOUNT_TOKEN=e35bx
WECHAT_ACCOUNT_AES_KEY=CYAOCD
我们为了让前后台用户都能扫码登录,将二维码等路由放前台,在前台路由中添加:
//easywechat
Route::get('/weixin', [WeixinController::class,'weixin'])->name('weixin');
Route::any('/wechat', [WeixinController::class,'serve'])->name('serve');
Route::get('/getwxpic', [WeixinController::class,'getWxPic'])->name('wx.pic');
Route::get('/loginCheck', [WeixinController::class,'loginCheck'])->name('home.login.check');
在app/Http/Controllers创建控制器WeixinController:
class WeixinController extends Controller
{
protected $app;
protected $successStatus = 200;
public function __construct()
{
$this->app = app('wechat.official_account');
}
//获取二维码图片
public function getWxPic(Request $request)
{
// 查询 cookie,如果没有就重新生成一次
if (!$cache_key = $request->cookie('wxcachekey')) {
$cache_key = 'wechat'.Str::random(10);//生成一个不重复的key作为cache标识
}
// 缓存微信带参二维码
if (!$url = Cache::get($cache_key)) {
// 有效期 1 天的二维码
$qrCode = $this->app->qrcode;
$result = $qrCode->temporary($cache_key, 3600 * 24);
$url = $qrCode->url($result['ticket']);
Cache::put($cache_key,$url,now()->addDay());
}
// 自定义参数返回给前端,前端轮询,并缓存cookie
return response()->json(['wxcachekey'=>$cache_key,'url'=>$url,'code' => 200], $this->successStatus)->cookie('wxcachekey', $cache_key, 24 * 60);
}
//前台微信用户登录检查
public function loginCheck(Request $request)
{
$wxcachekey = $request->wxcachekey.'_openid';
if(!$openid = Cache::get($wxcachekey)){
return response()->json(['code' => 500], 500);
}
if(!$user = User::where('openid',$openid)->first()) {
return response()->json(['code' => 500], 500);
}
// 登录用户、并清空缓存
Auth::login($user);
Cache::forget($wxcachekey);
return response()->json(['code' => 200], $this->successStatus);
}
//处理微信的请求消息,用户扫描二维码后处理
public function serve()
{
$app = $this->app;
$app->server->push(function ($message) {
if ($message) {
switch ($message['MsgType']) {
case 'event':
$func = strtolower($message['Event']);
return $this->$func($message);
break;
case 'text':
return '收到文字消息';
break;
case 'image':
return '收到图片消息';
break;
case 'voice':
return '收到语音消息';
break;
case 'video':
return '收到视频消息';
break;
case 'location':
return '收到坐标消息';
break;
case 'link':
return '收到链接消息';
break;
case 'file':
return '收到文件消息';
default:
return '收到其它消息';
break;
}
Log::info('无此处理方法:' . $method);
}
});
return $app->server->serve();
}
//关注事件
private function subscribe($message)
{
$openid = $message['FromUserName'];
if(!$user = User::where('openid',$openid)->first()){
//注册新用户
$customer = User::create([
'openid' => $openid,
'name' => '微信用户'.time(),
'phone' => '1'.time(),//手机号
'sex' => 1,//性别
'email' => time().mt_rand(1,100).'@qq.com',
'password' => Hash::make('123456'),//默认密码
'yhz' => Userzb::first()->id,//用户组
'email_verified_at' => now(),//默认激活
]);
}
//将二维码唯一标识和用户唯一openid对应,存入cache
$event_key = str_replace('qrscene_','',$message['EventKey']).'_openid';
if(!Cache::get($event_key)){
Cache::put($event_key, $openid, 4);
}
return '关注并登录成功';
}
//已关注扫码
private function scan($message)
{
$event_key = $message['EventKey'].'_openid';
$openid = $message['FromUserName'];
if(!Cache::get($event_key)){
Cache::put($event_key, $openid, 4);
}
return '扫码登录成功';
}
然后在后台路由中添加:
$router->get('/wxlogin', 'AuthController@loginCheck');//微信登录
并将此路由排除在授权外,config/admin.php文件:
'except' => [
'auth/login',
'auth/logout',
'wxlogin',
],
将原来后台的登录模板复制一份,放在前台resources/views下,并添加以下内容:
let timer = null;// 方便清除轮询
let key = null;
$('#wxlogin').on('click',function(){
// 请求登录二维码
$.ajax({
url:"{{ route('wx.pic') }}",
dataType:"json",
success:function(res){
console.log(res);
if (res.code !== 200)return;
var wxcachekey = res.wxcachekey;
$('.wechat-url').attr('src', res.url);//显示二维码图片
// 轮询登录状态
timer = setInterval(() => {
var islogin = "{{Admin::user() ? 1 : 0}}";
if(islogin == 1)location.href = '/admin';
// 请求参数是二维码中的场景值
$.ajax({
url: "/admin/wxlogin",
dataType: "json",
data: {wxcachekey: wxcachekey},
success: function(res){
console.log(res);
if(res.code == 200){
Dcat.success('登录成功');
location.href = res.url;
}else if(res.code == 300){
location.reload();//刷新页面绑定openid
}
clearInterval(timer);//清除轮询
}
});
}, 2000);
}
})
})
修改app/Admin/Controllers文件夹的AuthController,里面有借权中转微信登录的内容,主要是有些客户没有服务号,内容如下 :
namespace App\Admin\Controllers;
use Dcat\Admin\Models\Administrator as AdminUserModel;//引用model登录
use Dcat\Admin\Admin;
use Dcat\Admin\Form;
use Dcat\Admin\Layout\Content;
use Dcat\Admin\Models\Repositories\Administrator;
use Dcat\Admin\Traits\HasFormResponse;
use Illuminate\Auth\GuardHelpers;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Lang;
use Illuminate\Support\Facades\Redirect;
use Illuminate\Support\Facades\Validator;
use Dcat\Admin\Controllers\AuthController as BaseAuthController;
use App\Kzh\Curl_k;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Cookie;
use Log;
use Cache;
class AuthController extends BaseAuthController
{
use HasFormResponse;
protected $view = 'htlogin';
//后台微信用户登录检查
public function loginCheck(Request $request)
{
$wxcachekey = $request->wxcachekey.'_openid';
if(!$openid = Cache::get($wxcachekey)){
return response()->json(['code' => 500], 200);
}
if(!$check = AdminUserModel::whereJsonContains('openid', $openid)->first()) {
return response()->json(['code' => 500], 200);
}
// 登录用户、并清空缓存
session(['openid' => $openid]);//微信openid如果已存在用户中,则自动登录
if(!empty($check)){
$this->guard()->login($check);
session()->regenerate();
$url = $this->getRedirectPath();
Cache::forget($wxcachekey);
return response()->json(['code'=>200,'url'=>$url], 200);
}else{
//没有绑定账号,跳转绑定
return response()->json(['code'=>300], 200);
}
}
//重写登录逻辑
public function getLogin(Content $content){
if ($this->guard()->check()) {
return redirect($this->getRedirectPath());
}
$curl = new Curl_k();
$openid = session('openid',null);
if($curl->isWxClient() && empty($openid)){
$appid = env('WEIXIN_KEY');
$appKey = env('WEIXIN_SECRET');
$code = $_GET['code'] ?? null;
if(env('WEXIN_ON') == 'borrow'){
//没自己服务号,需要借权
if (empty($code)){
$bzurl = env('APP_URL').'/admin/auth/login';//本站url
$url = 'https://www.roujingmei.com/wxgetcode?url='.$bzurl;
Header("Location: $url");//跳转获取
}else{
$urlObj["appid"] = $appid;
$urlObj["secret"] = $appKey;
$urlObj["code"] = $code;
$urlObj["grant_type"] = "authorization_code";
$buff = "";
foreach ($urlObj as $k => $v){
if($k != "sign") $buff .= $k . "=" . $v . "&";
}
$bizString = trim($buff, "&");
$url = "https://api.weixin.qq.com/sns/oauth2/access_token?".$bizString;
$options = array();
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
if (!empty($options)) {
curl_setopt_array($ch, $options);
}
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
$res = curl_exec($ch);
curl_close($ch);
$data = json_decode($res,true);
$openid = $data['openid'] ?? null;//取出openid
}
}else{
//有自己的服务号
if (empty($code)){
$scheme = $_SERVER['HTTPS']=='on' ? 'https://' : 'http://';
$uri = $_SERVER['PHP_SELF'].$_SERVER['QUERY_STRING'];
if($_SERVER['REQUEST_URI']) $uri = $_SERVER['REQUEST_URI'];
$baseUrl = urlencode($scheme.$_SERVER['HTTP_HOST'].$uri);
$urlObj["appid"] = $appid;
$urlObj["redirect_uri"] = "$baseUrl";
$urlObj["response_type"] = "code";
$urlObj["scope"] = "snsapi_base";
$urlObj["state"] = "STATE"."#wechat_redirect";
$buff = "";
foreach ($urlObj as $k => $v){
if($k != "sign") $buff .= $k . "=" . $v . "&";
}
$bizString = trim($buff, "&");
$url = "https://open.weixin.qq.com/connect/oauth2/authorize?".$bizString;
Header("Location: $url");
exit();
}else{
$appid = env('WEIXIN_KEY');
$appKey = env('WEIXIN_SECRET');
$urlObj["appid"] = $appid;
$urlObj["secret"] = $appKey;
$urlObj["code"] = $code;
$urlObj["grant_type"] = "authorization_code";
$buff = "";
foreach ($urlObj as $k => $v){
if($k != "sign") $buff .= $k . "=" . $v . "&";
}
$bizString = trim($buff, "&");
$url = "https://api.weixin.qq.com/sns/oauth2/access_token?".$bizString;
$options = array();
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
if (!empty($options)) {
curl_setopt_array($ch, $options);
}
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
$res = curl_exec($ch);
curl_close($ch);
$data = json_decode($res,true);
$openid = $data['openid'] ?? null;
}
}
if(!empty($openid)){
session(['openid' => $openid]);//微信openid如果已存在用户中,则自动登录
$check = AdminUserModel::whereJsonContains('openid', $openid)->first();//微信openid已在用户中自动登录
if(!empty($check)){
$this->guard()->login($check);
session()->regenerate();
return redirect($this->getRedirectPath());
}
}
}
//如果openid没有绑定,则跳转绑定页面
return $content->full()->body(view($this->view,compact('openid')));
}
//登录认证
public function postLogin(Request $request)
{
$openid = session('openid',null);
$credentials = $request->only([$this->username(), 'password']);
$remember = (bool) $request->input('remember', false);
/** @var \Illuminate\Validation\Validator $validator */
$validator = Validator::make($credentials, [
$this->username() => 'required',
'password' => 'required',
]);
if ($validator->fails()) {
return $this->validationErrorsResponse($validator);
}
if ($this->guard()->attempt($credentials, $remember)) {
$username = $credentials['username'];
if(!empty($openid)){
//先获取用户openid,再追加更新Openid,实现多微信共账号登录
$old = AdminUserModel::where('username',$username)->first()->openid;
if(empty($old) or !in_array($openid,$old)){
$old[] = $openid;
AdminUserModel::where('username',$username)->update(['openid'=>$old]);
}
}
return $this->sendLoginResponse($request);
}
return $this->validationErrorsResponse([
$this->username() => $this->getFailedLoginMessage(),
]);
}
protected function getRedirectPath()
{
return $this->redirectTo ?: admin_url('/');
}
这样就实现了微信扫码自动登录功能了。