流程图

1、小程序客户端获取code、nick_name、头像。发起wx.request请求

2、服务器端携带数据(code+appId)向微信服务器端发起请求,获取open_id和session_key

2.1、完成注册或验证逻辑:服务器端注册账号,储存open_id、nick_name和头像信息。

2.2、向微信小程序返回token(token里包含user_id信息)

3、小程序客户端携带token访问服务器接口

3.1、验证服务器端token,如果有效,继续访问

3.2、如果token失效,刷新token,一次有效访问。然后返回新的token(token设置到header中)

3.3、小程序客户更新token。

 

步骤:

第一步:安装jwt

详见:laravel安装jwt

 

第二步:安装easy wechat

参考地址:https://blog.csdn.net/qq_39636712/article/details/109317190

2.1、设置小程序的app_id和secret

修改laravel配置文件/app/config/wechat.php

取消小程序配置变量注释。再到.env配置文件里添加环境变量

laravel+jwt+微信小程序授权登录_laravel小程序登录

上图是wechat.php文件

laravel+jwt+微信小程序授权登录_laravel小程序登录_02

上图是.env配置文件

 

第三步:laravel创建登录控制器ApiAuthorizationsController.php,登录逻辑

<?php
/**
 * Created by PhpStorm.
 * User: admin
 * Date: 2021/3/19
 * Time: 16:25
 */

namespace App\Http\Controllers\Api\User;


use App\Http\Controllers\Common\Result;
use App\Http\Controllers\Controller;
use App\Http\Controllers\Requests\AuthorizationLoginRequest;
use Illuminate\Auth\AuthenticationException;
use App\Models\User\UserInfoModel;
use Illuminate\Support\Facades\Log;

class ApiAuthorizationsController extends Controller
{


    /**
     * name:SWT用户登录,小程序微信登录
     */
    public function store(AuthorizationLoginRequest $request)
    {
        Log::debug($request->all());
        $code = $request->code;
        $nick_name = $request->nickName;
        $avator = $request->avator;
        // 根据 code 获取微信 openid 和 session_key
            $miniProgram = \EasyWeChat::miniProgram();
            $data = $miniProgram->auth->session($code);

        // 如果结果错误,说明 code 已过期或不正确,返回 401 错误
        if (isset($data['errcode'])) {
            return Result::fail('code不正确');
        }

        // 找到 openid 对应的用户
        $userInfo = UserInfoModel::where('open_id', $data['openid'])->first();

        $attributes['weixin_session_key'] = $data['session_key'];


        if(!$userInfo){
            //更新用户信息
            $userInfo = new UserInfoModel();
            $userInfo->open_id = $data['openid'];
            $userInfo->weixin_session_key = $data['session_key'];
            $userInfo->user_name = $nick_name;
            $userInfo->img = $avator;
            $userInfo->save();


        }else{
            // 更新用户数据
            $userInfo->update($attributes);
        }


        // 为对应用户创建 JWT
        $token = auth('api')->login($userInfo);
        return $this->respondWithToken($token)->setStatusCode(201);

    }



    /**
     * name:刷新token
     */
    public function update()
    {
        $token = auth('api')->refresh();
        return $this->respondWithToken($token);
    }

    /**
     * name:删除token
     * description:退出登录用的上
     */
    public function destroy()
    {
        auth('api')->logout();
        return response(null, 204);
    }


    /**
     * name:返回json格式
     */
    protected function respondWithToken($token,$is_register=false)
    {
        return response()->json([
            'code'=>200,
            'msg'=>'注册成功!',
            'access_token' => 'Bearer '.$token,
            'token_type' => 'Bearer',
            'expires_in' => auth('api')->factory()->getTTL() * 60
        ]);
    }


}

第四步:中间键刷新token文件。

4.1编辑RefreshToken.php

<?php

namespace App\Http\Middleware;

use App\Exceptions\InvalidRequestException;
use App\Http\Controllers\Common\Result;
use Closure;
//use Auth;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
use Tymon\JWTAuth\Exceptions\JWTException;
use Tymon\JWTAuth\Exceptions\TokenExpiredException;
use Tymon\JWTAuth\Http\Middleware\BaseMiddleware;

class RefreshToken extends BaseMiddleware
{

    public function handle(Request $request, Closure $next)
    {
        // 检查此次请求中是否带有 token,如果没有则抛出异常。
        try{
            $this->checkForToken($request);
        } catch (UnauthorizedHttpException $exception){
            throw new InvalidRequestException('token不能为空',401);
        }

        // 使用 try 包裹,以捕捉 token 过期所抛出的 TokenExpiredException  异常
        try {
            // 检测用户的登录状态,如果正常则通过
            if ($this->auth->parseToken()->authenticate()) {
                return $next($request);
            }
            throw new UnauthorizedHttpException('jwt-auth', '未登录');
        } catch (TokenExpiredException $exception) {
            // 此处捕获到了 token 过期所抛出的 TokenExpiredException 异常,我们在这里需要做的是刷新该用户的 token 并将它添加到响应头中
            try {
                // 刷新用户的 token
                $token = $this->auth->refresh();
                Log::debug('使用一次性登录以保证此次请求的成功');
                Auth::guard('api')->onceUsingId($this->auth->manager()->getPayloadFactory()->buildClaimsCollection()->toPlainArray()['sub']);
            } catch (JWTException $exception) {
                Log::debug('用户无法刷新令牌,需要重新登录');
                throw new InvalidRequestException('token过期,请重新登录',401);        //这个异常是自定义的**
                // 如果捕获到此异常,即代表 refresh 也过期了,用户无法刷新令牌,需要重新登录。
//                throw new UnauthorizedHttpException('jwt-auth', $exception->getMessage());
            }
        }

        return $this->setAuthenticationHeader($next($request), $token);

    }
}

4.2、重新定义中间件的执行顺序,配置App/Http/Kernel.php文件

<?php

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
     //省略的代码....
    //省略的代码....

        'api' => [
            \App\Http\Middleware\AcceptHeader::class,           //要在里面设置$request->headers->set('Accept', 'application/json');return $next($request);
//            \App\Http\Middleware\EnableCrossRequest::class,
            'throttle:api',
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],
    ];

    //重新定义中间件的执行顺序

    protected $middlewarePriority = [
        \Illuminate\Cookie\Middleware\EncryptCookies::class,
        \Illuminate\Session\Middleware\StartSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        \App\Http\Middleware\RefreshToken::class,
        \Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests::class,
        \Illuminate\Routing\Middleware\ThrottleRequests::class,
        \Illuminate\Session\Middleware\AuthenticateSession::class,
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
        \Illuminate\Auth\Middleware\Authorize::class,
    ];

    /**
     * The application's route middleware.
     *
     * These middleware may be assigned to groups or used individually.
     *
     * @var array
     */
    protected $routeMiddleware = [
        'auth' => \App\Http\Middleware\Authenticate::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
        'can' => \Illuminate\Auth\Middleware\Authorize::class,
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
        'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
        'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
        'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
        //token中间件
        'RefreshToken' => \App\Http\Middleware\RefreshToken::class,
    ];
}

4.3、路由配置api.php

<?php

use Illuminate\Routing\Router;


Route::group([
    'prefix'  => 'v1',
    'namespace' => '\\App\\Http\\Controllers\\Api',
],function (){
    Route::group([
        'prefix'=>'user',
        'namespace' => 'User'
    ],function (Router $router){
        //小程序用户登录
        $router->post('authorizations/login', 'ApiAuthorizationsController@store');
        $router->group([
            'middleware'=>['RefreshToken','auth:api']
        ],function(Router $router){
            //生成订单 这里就写一些授权登陆才能访问的页面
            $router->get('my/info','UserInfoController@myUserInfo');
        });

    });

});

4.4、自定义接口异常

<?php

namespace App\Exceptions;

use App\Http\Controllers\Common\Result;
use Exception;
use Illuminate\Http\Request;

class InvalidRequestException extends Exception
{
    public function __construct( $message = "", $code = 400)
    {
        parent::__construct($message, $code);
    }

    public function render(Request $request)
    {
        return response()->json(Result::create($this->code,$this->message), $this->code);
//        if ($request->expectsJson()) {
            // json() 方法第二个参数就是 Http 返回码
//            return response()->json(['msg' => $this->message], $this->code);
//            return response()->json(Result::create($this->code,$this->message), $this->code);
//        }
//        return view('pages.error', ['msg' => $this->message]);
    }
}

4.5、统一的返回接口,自定义

<?php
namespace App\Http\Controllers\Common;

use Log;

class Result
{
    /**
     * 返回错误信息数组,便于转换为JSON
     *
     * @param [string] $returnCode 返回的错误代码
     * @param object $returnData 返回的数据
     * @param object $returnMsg 返回的错误信息
     * @param object $url 跳转链接地址
     * @return void
     */
    public static function create($returnCode,  $returnMsg = '', $returnData = '', $url = '')
    {
        $result = [
            'code' => $returnCode,
            'msg' => $returnMsg,
            'data' => $returnData,
            'url' => $url
        ];
        return $result;
    }
}



第五步:小程序端创建登录文件

5.1、小程序登录文件

login/index.wxml

<view class="container">
  <view class="imagesize">
     <image src="../../images/logo.jpg"></image>
  </view>
  <view class="userinfo">
    <block>
      <button class="weui-btn weui-btn_primary"  wx:if="{{canIUseGetUserProfile}}" bindtap="getUserProfile"> 用户授权登录 </button>
    </block>
  </view>
</view>

login/index.js

// 获取应用实例
const app = getApp()

Page({
  data: {
    userInfo: {},
    hasUserInfo: false,
    canIUseGetUserProfile: false,
  },
  onLoad() {
    wx.setNavigationBarTitle({
      title: app.globalData.apiUrl //页面标题为路由参数,根据自己需求来修改
    });
    if (wx.getUserProfile) {
      this.setData({
        canIUseGetUserProfile: true
      })
    }
  },
  getUserProfile(e) {
    // 推荐使用wx.getUserProfile获取用户信息,开发者每次通过该接口获取用户个人信息均需用户确认
    // 开发者妥善保管用户快速填写的头像昵称,避免重复弹窗
    wx.getUserProfile({
      desc: '用于完善会员资料', // 声明获取用户个人信息后的用途,后续会展示在弹窗中,请谨慎填写
      success: (res) => {
        var nick_name = res.userInfo.nickName;
        var avator = res.userInfo.avatarUrl;
        //然后授权登录
        wx.login({
          success (res) {
            if (res.code) {
              //发起网络请求
              wx.request({
                url: 'https://www.***.com/api/v1/user/authorizations/login',
                method:'POST',
                header: {//请求头
                  "Content-Type": "application/json"
                },
                dataType:'json',
                data: {
                  code: res.code,
                  nickName:nick_name,
                  avator:avator
                },
                success:function(res){
                  //将token保存到storage中
                  console.log(res.data);
                  try{
                    wx.setStorageSync('access_token',res.data.access_token);
                    wx.setStorageSync('nick_name',nick_name);
                    wx.setStorageSync('avator',res.avator);
                    wx.switchTab({
                      url: '../index/index',
                    });
                  }catch(e){
                    console.log('storage数据写入失败!');
                  };
                },
                fail: function (err) { 
                  console.log('请求失败');
                },//请求失败
                complete: function () { }//请求完成后执行的函数




              })
            } else {
              console.log('登录失败!' + res.errMsg)
            }
          }
        })

      
      }
    })
    
  },
  getUserInfo(e) {
    // 不推荐使用getUserInfo获取用户信息,预计自2021年4月13日起,getUserInfo将不再弹出弹窗,并直接返回匿名的用户个人信息
    this.setData({
      userInfo: e.detail.userInfo,
      hasUserInfo: true
    })
  },
})

5.2、token过期后,小程序刷新

// index.js
// 获取应用实例
const app = getApp()

Page({
  data: {
    userInfo: {},
    hasUserInfo: false,
    canIUse: wx.canIUse('button.open-type.getUserInfo'),
    canIUseGetUserProfile: false,
    canIUseOpenData: wx.canIUse('open-data.type.userAvatarUrl') && wx.canIUse('open-data.type.userNickName'), // 如需尝试获取用户信息可改为false
    activeTitle:'weixin',
    activeThumb:''

  },

 
  onLoad() {
    var that = this;
    //如果不能处在token,返回登录页面
    if(!wx.getStorageSync('access_token')){
      wx.redirectTo({
        url: '../login/index',
      });
    }

    //获取用户信息
    wx.request({
      url: app.globalData.apiUrl+'api/v1/user/my/info',
      header: {//请求头
        "Content-Type": "application/json",
        "Authorization": wx.getStorageSync('access_token'),
      },
      dataType:'json',
      success:function(res){
        if(typeof res.header.Authorization!='undefined'){   //如果有token返回,将最新的token存入缓存。
          wx.setStorageSync('access_token',res.header.Authorization);
        }
        if(res.data.code=='401'){
          console.log('重新重新登录');
          wx.redirectTo({
            url: '../login/index'
          });
           return;
        }
        //一些操作
      },
      fail: function (err) { 
        console.log('请求失败');
        wx.redirectTo({
          url: '../login/index'
        });
         return;
      },//请求失败
      complete: function () { 
      }//请求完成后执行的函数
    })

  },

  //页面跳转
  paySuccess : function(){
    wx.showToast({
        title: '捐款成功!',
        icon: 'success',
        duration: 3000
    });
    wx.navigateTo({
      url: '../show/success',
    });
  }

  
})

 

 

内容还不一定够完善,提供参考