laravel:用户认证之 lcobucci/jwt 4.0.4(10.27.0)

一,安装第三方库 

1,官方代码站:

https://github.com/lcobucci/jwt

文档地址:

https://lcobucci-jwt.readthedocs.io/en/latest/

2,用composer安装

liuhongdi@lhdpc:/data/laravel/dignews$ composer require lcobucci/jwt

3,安装后查看库的版本:

liuhongdi@lhdpc:/data/laravel/dignews$ composer show lcobucci/jwt
name     : lcobucci/jwt
descrip. : A simple library to work with JSON Web Token and JSON Web Signature
keywords : JWS, jwt
versions : * 4.0.4
type     : library
...

二,php代码

1,封装类:App\extend\jwt\JwtUtil.php

<?php

namespace App\extend\jwt;

use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\Signer\Key\InMemory;
/**
 * 单例模式
 * Class JwtUtil
 * @package App\extend\jwt
 */
class JwtUtil
{
    private $config;
    private $key = "Ge1KCTRhdVsmUUZY0GrwgEvLubPvLOCM";
    private $iss = "liuhongdi.com";//颁发者(iss声明)
    private $aud = "liuhongdiauth.com";//访问群体(aud声明)
    private $jti = "5t6y9400453"; //id(jti声明)
    private $expTime = 1;//令牌有效时间,单位小时
    private static $instance;// 单例模式JwtAuth句柄

    // 获取JwtAuth的句柄
    public static function getInstance()
    {
        if (is_null(self::$instance)) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    /**
     * 构造
     */
    public function __construct()
    {
        self::init();
    }

    /**
     * 初始化
     */
    private function init()
    {
        $config = Configuration::forSymmetricSigner(
            new Sha256(),
            InMemory::base64Encoded($this->key)
        );
        $this->config = $config;
    }

    /**
     * 创建JWT
     * @param array $arrClaim
     * @return string
     * @throws \Exception
     * @author 老刘
     */
    public function createToken(array $arrClaim)
    {
        $config = $this->config;
        assert($config instanceof Configuration);
        if (is_array($arrClaim) && count(array_filter(array_keys($arrClaim),'is_string'))>0) {
            //是关联数组
        } else {
            //不是关联数组
            throw new \Exception("claim参数必须为关联数组");
        }

        $now = new \DateTimeImmutable();

        $token = $config->builder()
            // 配置颁发者(iss声明)
            ->issuedBy($this->iss)
            // 配置访问群体(aud声明)
            ->permittedFor($this->aud)
            // 配置id(jti声明)
            ->identifiedBy($this->jti)
            // 配置令牌发出的时间(iat声明)
            ->issuedAt($now)
            // 配置令牌的过期时间(exp claim)
            ->expiresAt($now->modify("+{$this->expTime} hour"));
        //claim
        foreach ($arrClaim as $k => $item) {
            $token = $token->withClaim($k, $item);
        }
        // 生成新令牌
        $token = $token->getToken($config->signer(), $config->signingKey());
        return $token->toString();
    }

    /**
     * 解析token
     * @param string $jwt
     * @return mixed
     * @author 老刘
     */
    public function parseToken(string $jwt)
    {
        $config = $this->config;
        $token = $config->parser()->parse($jwt);
        return $token->claims();
    }


    /**
     * 验证令牌
     * @param $jwt
     * @return mixed
     * @throws \Exception
     * @author 老刘
     */
    public function validatorToken($jwt)
    {
        $config = $this->config;
        $token = $config->parser()->parse($jwt);
        $claims = $token->claims();
        $jti = (string)$claims->get('jti');
        $iss = (string)$claims->get('iss');
        $aud = $claims->get('aud');
        $exp = $claims->get('exp');
        $now = new \DateTimeImmutable();
        // 是否过期
        if ($exp < $now) {
            throw new \Exception("身份已过期");
        }
        //验证jwt id是否匹配
        $validate_jwt_id = new \Lcobucci\JWT\Validation\Constraint\IdentifiedBy($jti);
        // 验证签发人url是否正确
        $validate_issued = new \Lcobucci\JWT\Validation\Constraint\IssuedBy($iss);
        // 验证客户端url是否匹配
        $validate_aud = new \Lcobucci\JWT\Validation\Constraint\PermittedFor($aud[0]);
        $config->setValidationConstraints($validate_jwt_id, $validate_issued, $validate_aud);
        $constraints = $config->validationConstraints();
        //验证
        if (!$config->validator()->validate($token, ...$constraints)) {
            throw new \Exception("非法的请求");
        }
        return $claims;
    }
}

2,创建controller:

liuhongdi@lhdpc:/data/laravel/dignews$ php artisan make:controller LoginController

   INFO  Controller [app/Http/Controllers/LoginController.php] created successfully.

代码:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\extend\result\Result;
use App\Models\Staff as StaffModel;
use App\business\StaffLogin;
use Illuminate\Support\Facades\Redis;
use App\extend\jwt\JwtUtil;

class LoginController extends Controller
{
    /**
     * 得到用户信息
     */
    public function info(Request $request)
    {
        //得到数据库的用户id
        $staffId = $request->auth;
        $staff = new StaffModel();
        $rows = $staff->getOneByStaffId($staffId);
        if (is_null($rows)) {
            return Result::ErrorCode(100,'用户不存在');
        }
        unset($rows['password']);
        //添加头像
        $host = "http://192.168.219.6/imgstaffhead";
        $rand = rand(1000,9999);
        $rows['head'] = $host."/".$staffId.".jpg?rand=".$rand;
        //返回
        return Result::Success($rows);
    }

    //得到jwt信息,返回给线下
    function getJwtInfo($userId,$nickName) {
        //验证成功,生成jwt返回
        //$jUtil = new JwtUtil();
        //$token = $jUtil->createJwt($userId);
        $token = JwtUtil::getInstance()->createToken(['uid' => $userId]);
        $res = ["tokenString"=>$token];

        $host = "http://192.168.219.6/imgstaffhead";
        $rand = rand(1000,9999);
        $res['head'] = $host."/".$userId.".jpg?rand=".$rand;
        $res['nickname'] = $nickName;
        return $res;
    }
    
    /**
     * 登录
     *
     */
    public function login(Request $request) {
        
        $username = $request->post('username');
        $password = $request->post('password');

        $uniqid = $request->post('uniqid');
        $captcode = $request->post('captcode');
        //检查验证码:
        $value = Redis::get($uniqid);
        if (strtolower($captcode) !== strtolower($value)) {
            return Result::ErrorCode(400,"输入的验证码不正确");
        }

        //用username读取用户信息
        $staff = new StaffModel();
        $rows = $staff->getOneByUsername($username);
        if ($rows == null) {
            return Result::ErrorCode(422,'用户名出现错误');
        } else {
            if ($rows['is_active'] == 0) {
                return Result::ErrorCode(422,'用户未激活');
            } else {
                //检查密码是否正确
                if (password_verify($password,$rows['password'])) {
                    $res = $this->getJwtInfo($rows['staff_id'],$rows['nickname']);
                    //登录成功,写日志
                    $loginLog = new StaffLogin();
                    $loginLog->log($rows['staff_id']);
                    return Result::Success($res);
                } else {
                    //报密码错误
                    return Result::ErrorCode(422,"用户名密码错误");
                }
            }
        }
    }
}

3,创建middleware

liuhongdi@lhdpc:/data/laravel/dignews$ php artisan make:middleware JwtAuth

   INFO  Middleware [app/Http/Middleware/JwtAuth.php] created successfully.

代码:

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

use App\extend\jwt\JwtUtil;

class JwtAuth
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next): Response
    {
        //得到jwt的token
        $auth = $request->header('authorization');
        if ($auth == null) {
            return $next($request);
        }
        $token = str_replace("Bearer ","",$auth);
        if (count(explode('.', $token)) <> 3) {
            throw new \Exception('非法身份信息,请重新登录', -1);
        }
        //检验token
        $objJwtAuth = JwtUtil::getInstance();
        $claims = $objJwtAuth->validatorToken($token);
        //得到用户id
        $uid = $claims->get('uid');
        //添加到request
        $request->merge(['auth'=>$uid]);
        return $next($request);
    }
}

三,vue代码:

api/axios.js

import axios from 'axios'
import { ElMessage } from "element-plus";
import router from "../route/router.js"
import { getUser } from '@/utils/user'

import { showLoading, hideLoading } from '@/utils/loading'
let config = {
    timeout:10000,
};

const _axios = axios.create(config);
//request interceptor
_axios.interceptors.request.use(
    function(config) {
        showLoading();
        //在发送请求前,增加token字串到header
        let userInfo = getUser().data;
        if ('tokenString' in userInfo && userInfo["tokenString"] != ""){
            config.headers.Authorization ="Bearer "+userInfo["tokenString"];
        }
      return config;
    },
    function(error) {
      // Do something with request error
      return Promise.reject(error);
    }
);

//response interceptor
_axios.interceptors.response.use(
    function(response) {
        hideLoading();
        let code = response.data.code;
        if (code == '401') {
            //现在跳转去登录
            let redi = {path: router.currentRoute.value.fullPath};
            let redistr = JSON.stringify(redi);
            if (router.currentRoute.value.meta.redirectstr) {
                redistr = router.currentRoute.value.meta.redirectstr;
            }
            //打印出参数
            if (router.currentRoute.value.meta.actstr) {
                let actstr = router.currentRoute.value.meta.actstr;
                router.replace({ path: '/login/login',query: { redirect: redistr,act:actstr } });
            } else {
                router.replace({ path: '/login/login',query: { redirect: redistr } });
            }

        }
      return response;
    },
    function(error) {
        hideLoading();
      // Do something with response error
        if (error.response.status) {
            //alert(error.response.status);
            switch (error.response.status) {
                // 401: 未登录
                // 未登录则跳转登录页面,并携带当前页面的路径
                // 在登录成功后返回当前页面,这一步需要在登录页操作。
                case 401:
                    router.replace({ path: '/login/login' });
                    break;
                // 403 token过期
                // 登录过期对用户进行提示
                // 清除本地token和清空vuex中token对象
                // 跳转登录页面
                case 403:
                    ElMessage.error("登录过期,请重新登录");
                    // 清除token
                    localStorage.removeItem('token')
                    // 跳转登录页面,并将要浏览的页面fullPath传过去,登录成功后跳转需要访问的页面
                    setTimeout(() => {
                        router.push({ path: '/login/login' });
                    }, 1000)
                    break;
                // 404请求不存在
                case 404:
                    ElMessage.error("网络请求不存在,404错误");
                    break;
                // 500服务端错误
                case 500:
                    ElMessage.error("服务端有故障,500错误");
                    break;
                // 其他错误,直接抛出错误提示
                default:
                    var tip = "";
                    if (typeof(error.response.data.message) == "undefined") {
                        tip = error.toString();
                    } else {
                        tip = error.response.data.message;
                    }
                    ElMessage.error(tip);
                    break;
            }
            return Promise.reject(error.response)
        }
    }
);

/**
 * get方法,对应get请求
 * @param {String} url [请求的url地址]
 * @param {Object} params [请求时携带的参数]
 */
export function get (url, params) {
  return new Promise((resolve, reject) => {
    _axios.get(url, {
      params: params,
    }).then(res => {
      resolve(res.data)
    }).catch(err => {
      reject(err.data)
    })
  })
}

//postForm
export function post (url, params) {
  return new Promise((resolve, reject) => {
    _axios.post(url, params,{headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
    }})
        .then(res => {
          resolve(res.data)
        })
        .catch(err => {
          reject(err.data)
        })
  })
}

说明:刘宏缔的架构森林—专注it技术的博客,
网址:https://imgtouch.com
本文: https://blog.imgtouch.com/index.php/2023/10/31/laravel-yong-hu-ren-zheng-zhi-lcobucci-jwt-10-27/
代码: https://github.com/liuhongdi/https://gitee.com/liuhongdi
说明:作者:刘宏缔 邮箱: 371125307@qq.com

四,测试效果:

五,查看laravel框架的版本:

liuhongdi@lhdpc:/data/laravel/dignews$ php artisan --version
Laravel Framework 10.27.0
QR:laravel:用户认证之 lcobucci/jwt 4.0.4(10.27.0)

发表回复