1. 配置中间件
1.1 创建中间键
php think make:middleware Check
<?php
namespace app\http\middleware;
use app\common\TokenVerify;
use think\facade\Request;
class Check
{
// 放行的baseUrl
private static array $release = [
// 放行用户进行注册
'http://rangeloney.com/index.php/user/index/register',
'https://rangeloney.com/index.php/user/index/register',
// 测试时放行 login
// 'http://rangeloney.com/index.php/user/index/login',
// 'https://rangeloney.com/index.php/user/index/login',
];
// 入口方法
public function handle($request, \Closure $next)
{
// 获取 baseUrl,不要查询字符串
$baseUrl = Request::baseUrl(true);
// 请求放行
if (in_array($baseUrl, self::$release) || !isset($token) || $token === '') {
return $next($request);
}
// 获取请求中的 token 和 uid
$token = $request->header('token');
$back = []; // 定义一个用于返回消息的数组
// token 验证
$res = (new TokenVerify())->checkToken($token);
if (!$res) {
list($back['status'], $back['msg']) = ['101', 'token验证失败'];
} else { // 验证成功则用户不需要登录,前台控制页面的跳转
list($back['status'], $back['msg']) = ['100', 'token验证成功'];
}
return json($back);
}
}
1.2 注册中间键
application 目录下创建 middleware.php ,再其中注册中间件
<?php
return [
// 注册这个 check 中间件
'check' => app\http\middleware\Check::class,
];
2. Model
在 common文件下建立 user 模型
<?php
namespace app\common\model;
use think\db\exception\DataNotFoundException;
use think\db\exception\ModelNotFoundException;
use think\exception\DbException;
use think\Model;
class User extends Model
{
// 设置当前模型对应的完整数据表名称
protected string $table = 'user';
// 设置主键
protected string $pk = 'IDX';
/**
* 根据用户名和密码查询单个用户信息
* @param $username
* 用户名
* @param $password
* 密码
* @return array|false|string
* 查询成功返回处理后的数组数据,失败返回false
*/
public function findUserInfoByUserNameAndPwd($username, $password)
{
try {
$user = User::where([
'USERNAME' => $username,
'PASSWORD' => $password
])->findOrEmpty();
} catch (DataNotFoundException $e) {
return '数据未查到:' . $e;
} catch (ModelNotFoundException $e) {
return '模型未查到:' . $e;
} catch (DbException $e) {
return '数据库连接异常' . $e;
}
// 处理一下数据返回给调用它的控制器
return ($user->isEmpty()) ? false :
[
'username' => $user['USERNAME'],
'password' => $user['PASSWORD'],
'identity' => $user['IDENTITY'],
'power' => $user['JURISDICTION'],
'latestLoginTime' => $user['LATEST_LOGIN_TIME'],
'id' => $user['IDX']
];
}
}
3. Login 方法
application 目录下创建user模块,user模块下创建Index类
<?php
namespace app\user\controller;
use app\common\TokenVerify;
use think\Controller;
class Index extends Controller
{
/**
* 用户登录:用户名密码正确后签发 token 给客户端端
* @return string|
*/
public function Login()
{
$username = input('username'); // 获取用户名
$password = input('password'); // 获取密码
// 获取用户信息
$info = model('common/User')->findUserInfoByUserNameAndPwd($username, $password);
// 未查到相关信息
if (!$info) {
return json([
'status' => '102',
'msg' => '密码或用户名输入错误'
]);
}
// 签发token给客户端
return json((new TokenVerify)->creatToken($info));
}
// 用户注册
public function Register()
{
return '进行用户注册';
// 用户名
// 手机号
// 密码
}
}
4. Token 验证类
application 目录下创建爱你 common 文件夹下 创建 TokenVerify类
<?php
namespace app\common;
use Exception;
use think\Controller;
use \Firebase\JWT\JWT;
use think\facade\Cache;
class TokenVerify extends Controller
{
// redis 键前缀
private static string $redisPrefix = 'xs';
// redis 过期时间
private static int $redisExpire = 7200;
// secret
private static string $secret = '214';
/**
* 于生成一个 token
* @param $info
* 需要用户的部分信息
* @return array
* 返回一个数组,包含:token、uid、身份、权限
*/
public function creatToken($info)
{
$secret = self::$secret; // secret
$time = time(); // 当前时间
$uid = md5($info['id'] . $info['username'] . $time); // 生成uid
$payload = [ // payload
'iss' => 'http://rangeloney.com', // 签发者 可选
'aud' => [ // 接收该JWT的一方,可选
'identity' => $info['identity'], // 用户身份
'power' => $info['power'] // 权限数字
],
'iat' => $time, // 签发时间
'nbf' => $time, // 某个时间点后才能访问,比如设置time+30,表示当前时间30秒后才能使用
'exp' => $time + 3600 * 2, // 过期时间,这里设置2个小时
'data' => [ // 自定义信息
'uid' => $uid,
'username' => $info['username']
]
];
// 将这个 token 存到 redis中,设置键为:前缀 + 用户名,设置值为 uid
$key = self::$redisPrefix . '_' . $info['username'];
Cache::store('redis')->set($key, $uid, self::$redisExpire);
// 返回 token (将其他信息都存在了token中,我并不需要额外设置)
$token = JWT::encode($payload, $secret, 'HS256');
return [
'token' => $token, // token
];
}
/**
* token验证
* @param $token
* 客户端传入的 token
* @return bool
* 返回 true 验证通过,返回 false 验证位通过
*/
public function checkToken($token)
{
try {
$clientToken = JWT::decode($token, self::$secret, ['HS256']); // 解码来自客户端的 token
$tokenArr = self::object_array($clientToken); // stdClass 转 array
list($username, $uid) = [$tokenArr['data']['username'], $tokenArr['data']['uid']];
$redis = Cache::store('redis')->handler();
$key = self::$redisPrefix . '_' . $username;
// 如果这个键不存在则验证失败
if (!$redis->exists($key)) {
return false;
}
// 从新设置这个 token 的过期时间,达到延长的效果
Cache::store('redis')->set($key, $uid, self::$redisExpire);
} catch (Exception $e) {
return false;
}
return true;
}
// PHP stdClass Object转 array
private static function object_array($array)
{
if (is_object($array)) {
$array = (array)$array;
}
if (is_array($array)) {
foreach ($array as $key => $value) {
$array[$key] = self::object_array($value);
}
}
return $array;
}
}