本文已参与「新人创作礼」活动,一起开启掘金创作之路。
1. 前言
firebase/php-jwt 是一个非常简单的 JWT 库,用于在 PHP 中对 JSON Web令牌(JWT)进行编码和解码
packagist 上的下载次数更是达到了 1亿 以上,可见该扩展包受欢迎的程度
本文记录使用 ThinkPHP6.0 开发微信小程序接口时如何使用 JWT 做的接口鉴权
composer create-project topthink/think:"6.0.*"
cd think
composer require firebase/php-jwt:"6.x"
观看本文前首先要明白一个概念: TP6.0 中控制器的构造方法、控制器中间件的执行顺序
控制器构造方法 > 控制器中间件 > 控制器方法
2. 过期时间
在 \Firebase\JWT\JWT::decode() 方法中,可以发现以下代码
当 $payload 中有 exp 属性时,则判断 token 是否过期
当 $payload 中没有传入 exp 属性时,则 token 可以永久使用
// Check if this token has expired.
if (isset($payload->exp) && ($timestamp - static::$leeway) >= $payload->exp) {
throw new ExpiredException('Expired token');
}
3. 代码示例
公共基础控制器构造方法 Base.php
protected $middleware = [JwtMiddleware::class];
public function __construct(Request $request)
{
$token = $request->header('token');
if (empty($token)) {
$request->uid = 0;
} else {
$request->uid = JwtAuth::decode($token);
}
}
创建中间件 JwtMiddleware.php
public function handle($request, \Closure $next)
{
// 因为构造方法优先于控制器中间件执行
// 如果 $request->uid 已存在,代表已在构造方法中获取了用户id,无需再次对token解密
if (!empty($request->uid)) {
return $next($request);
}
// 执行到此代表请求头中的 token 为空
throw new \Exception('请先登录');
// 继续执行请求
return $next($request);
}
Jwt 功能封装类 JwtAuth.php
<?php
declare(strict_types=1);
namespace app\lib;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
class JwtAuth
{
// 访问密钥
const KEY = 'ed6a18a9a';
// 签发者
const ISS = 'liang';
// 接收者
const AUD = 'www.itqaq.com';
// 加密算法 The signing algorithm
const ALG = 'HS256';
/**
* 对数据进行编码
*
* @param integer $uid
*/
public static function encode(int $uid)
{
$time = time();
$payload = [
"iss" => self::KEY,
"aud" => self::KEY,
"iat" => $time,
"nbf" => $time,
'exp' => $time + 86400 * 30,
'data' => ['uid' => $uid],
];
$token = JWT::encode($payload, self::KEY, self::ALG);
return $token;
}
/**
* 对 token 进行编码
*
* @param string $token
* @param integer $user_id
*/
public static function decode(string $token)
{
try {
// 对 token 进行编码
$decoded = JWT::decode($token, new Key(self::KEY, self::ALG));
// 检测 token 附加数据中是否存在用户id
if (isset($decoded->data->uid) && $decoded->data->uid > 0) {
$user_id = intval($decoded->data->uid);
} else {
throw new \Exception('token 中没有用户id');
}
} catch (\Exception $e) {
throw new \Exception($e->getMessage(), 201);
}
return $user_id; // 用户id
}
}
4. 使用说明
通过上面代码可以看到基础控制器 Base.php 中定义了控制器中间件,需要登录状态校验的控制器要继承 Base 控制器即可
场景一: 控制器中的所有方法都要进行登录状态校验,也就是只有登录了才能访问,则直接继承即可
use app\Request;
class User extends Base
{
public function getProfile(Request $request)
{
$request->uid; // 用户id
}
}
场景二: 控制器中一部分方法必须登录了才能访问,一部分方法有没有登录都可以访问
此时需要继承 Base 控制器,并且重写 $middleware 属性
有没有登录都能访问的方法使用 except 指定即可,此时 $request->uid 值为 0 或 用户id
use app\Request;
class User extends Base
{
protected $middleware = [
JwtMiddleware::class => [
// getLists 方法不执行中间件
'except' => ['getLists'],
],
];
/**
* 有没有登录都可以访问
*
* @param Request $request
*/
public function getLists(Request $request)
{
$request->uid; // 没有登录时值为 0 已登录则值为用户id
}
/**
* 已登录才能访问
*
* @param Request $request
*/
public function getProfile(Request $request)
{
$request->uid; // 用户id
}
}