本文目录导读:

在ThinkPHP项目中,实现令牌(Token)的刷新与过期管理是一个常见需求,特别是在前后端分离的应用中,以下是几种常见的实现方案:
使用JWT(推荐方案)
安装依赖
composer require firebase/php-jwt
JWT工具类封装
<?php
// app/common/service/JwtService.php
namespace app\common\service;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use think\facade\Cache;
class JwtService
{
// 密钥
private $secretKey;
// 访问令牌有效时间(秒)- 默认2小时
private $accessTokenExpire = 7200;
// 刷新令牌有效时间(秒)- 默认7天
private $refreshTokenExpire = 604800;
public function __construct()
{
$this->secretKey = config('jwt.secret_key');
}
/**
* 生成令牌对
* @param array $userInfo 用户信息
* @return array
*/
public function generateTokenPair($userInfo)
{
$accessToken = $this->generateAccessToken($userInfo);
$refreshToken = $this->generateRefreshToken($userInfo);
return [
'access_token' => $accessToken,
'refresh_token' => $refreshToken,
'token_type' => 'Bearer',
'expires_in' => $this->accessTokenExpire
];
}
/**
* 生成访问令牌
*/
public function generateAccessToken($userInfo)
{
$payload = [
'user_id' => $userInfo['id'],
'user_name' => $userInfo['username'],
'token_type' => 'access',
'iat' => time(), // 签发时间
'exp' => time() + $this->accessTokenExpire // 过期时间
];
return JWT::encode($payload, $this->secretKey, 'HS256');
}
/**
* 生成刷新令牌
*/
public function generateRefreshToken($userInfo)
{
// 生成唯一标识
$tokenId = md5(uniqid($userInfo['id'], true));
$payload = [
'user_id' => $userInfo['id'],
'token_id' => $tokenId,
'token_type' => 'refresh',
'iat' => time(),
'exp' => time() + $this->refreshTokenExpire
];
// 将刷新令牌ID存储到Redis,便于管理
$cacheKey = 'refresh_token:' . $tokenId;
Cache::set($cacheKey, $userInfo['id'], $this->refreshTokenExpire);
$refreshToken = JWT::encode($payload, $this->secretKey, 'HS256');
return $refreshToken;
}
/**
* 验证令牌
* @param string $token 令牌
* @param string $type 令牌类型
* @return array|false
*/
public function verifyToken($token, $type = 'access')
{
try {
$decoded = JWT::decode($token, new Key($this->secretKey, 'HS256'));
// 检查令牌类型
if ($decoded->token_type !== $type) {
return false;
}
// 检查是否过期
if ($decoded->exp < time()) {
return false;
}
// 如果是刷新令牌,检查是否存在
if ($type === 'refresh') {
$cacheKey = 'refresh_token:' . $decoded->token_id;
if (!Cache::get($cacheKey)) {
return false;
}
}
return (array)$decoded;
} catch (\Exception $e) {
return false;
}
}
/**
* 刷新令牌
* @param string $refreshToken 刷新令牌
* @return array|false
*/
public function refreshToken($refreshToken)
{
// 验证刷新令牌
$claims = $this->verifyToken($refreshToken, 'refresh');
if (!$claims) {
return false;
}
// 获取用户信息
$userId = $claims['user_id'];
$user = \think\facade\Db::name('users')->find($userId);
if (!$user) {
return false;
}
// 删除旧的刷新令牌
$oldTokenId = $claims['token_id'];
Cache::delete('refresh_token:' . $oldTokenId);
// 生成新的令牌对
return $this->generateTokenPair($user);
}
/**
* 注销令牌
*/
public function logout($token)
{
try {
$claims = $this->verifyToken($token);
if ($claims && $claims['token_type'] === 'access') {
// 简单方案:将access token加入黑名单缓存
$cacheKey = 'blacklist_token:' . md5($token);
Cache::set($cacheKey, true, 3600);
}
return true;
} catch (\Exception $e) {
return false;
}
}
}
配置类
<?php
// config/jwt.php
return [
'secret_key' => env('JWT_SECRET_KEY', 'your-secret-key'),
'access_token_expire' => env('JWT_ACCESS_EXPIRE', 7200),
'refresh_token_expire' => env('JWT_REFRESH_EXPIRE', 604800)
];
中间件实现
<?php
// app/middleware/AuthMiddleware.php
namespace app\middleware;
use think\facade\Request;
use think\facade\Config;
use app\common\service\JwtService;
class AuthMiddleware
{
public function handle($request, \Closure $next)
{
// 获取Authorization Header
$authorization = $request->header('Authorization');
if (empty($authorization)) {
return json(['code' => 401, 'message' => '请先登录'], 401);
}
// 解析token
$token = str_replace('Bearer ', '', $authorization);
$jwtService = new JwtService();
$claims = $jwtService->verifyToken($token, 'access');
if (!$claims) {
return json(['code' => 401, 'message' => '令牌无效或已过期'], 401);
}
// 将用户信息绑定到request
$request->user = $claims;
return $next($request);
}
}
控制器使用示例
<?php
// app/controller/Login.php
namespace app\controller;
use app\common\service\JwtService;
use think\facade\Request;
use think\facade\Db;
class Login
{
protected $jwtService;
public function __construct()
{
$this->jwtService = new JwtService();
}
/**
* 登录接口
*/
public function login()
{
$username = Request::param('username');
$password = Request::param('password');
// 验证用户
$user = Db::name('users')->where('username', $username)->find();
if (!$user || !password_verify($password, $user['password'])) {
return json(['code' => 400, 'message' => '用户名或密码错误'], 400);
}
// 生成令牌
$tokenData = $this->jwtService->generateTokenPair($user);
return json([
'code' => 200,
'message' => '登录成功',
'data' => $tokenData
]);
}
/**
* 刷新令牌接口
*/
public function refresh()
{
$refreshToken = Request::param('refresh_token');
if (empty($refreshToken)) {
return json(['code' => 400, 'message' => '缺少刷新令牌'], 400);
}
$newTokens = $this->jwtService->refreshToken($refreshToken);
if (!$newTokens) {
return json(['code' => 401, 'message' => '刷新令牌无效或已过期'], 401);
}
return json([
'code' => 200,
'message' => '令牌刷新成功',
'data' => $newTokens
]);
}
/**
* 获取用户信息(需要认证)
*/
public function userInfo()
{
$userInfo = Request::user;
return json([
'code' => 200,
'data' => $userInfo
]);
}
/**
* 更新用户信息(需要认证)
*/
public function updateInfo()
{
$userInfo = Request::user;
$userId = $userInfo['user_id'];
// 更新用户信息逻辑...
return json([
'code' => 200,
'message' => '更新成功'
]);
}
/**
* 注销登录
*/
public function logout()
{
$authorization = Request::header('Authorization');
$token = str_replace('Bearer ', '', $authorization);
$this->jwtService->logout($token);
return json([
'code' => 200,
'message' => '注销成功'
]);
}
}
路由配置
// route/app.php
use think\facade\Route;
// 公开接口
Route::post('login', 'Login/login');
Route::post('refresh', 'Login/refresh');
// 需要认证的接口
Route::group(function () {
Route::get('user/info', 'Login/userInfo');
Route::post('user/update', 'Login/updateInfo');
Route::post('logout', 'Login/logout');
})->middleware(\app\middleware\AuthMiddleware::class);
使用ThinkPHP自带的Session实现
对于传统Web应用,可以使用基于Session的认证:
<?php
// app/controller/Login.php
namespace app\controller;
use think\facade\Session;
use think\facade\Request;
class Login
{
public function login()
{
// 验证用户...
// 设置Session
Session::set('user', [
'id' => $user['id'],
'username' => $user['username']
]);
// 设置过期时间(可选)
Session::set('session_expire', time() + 3600);
return json(['code' => 200, 'message' => '登录成功']);
}
public function checkSession()
{
$user = Session::get('user');
// 检查session是否过期
$expireTime = Session::get('session_expire');
if ($expireTime && $expireTime < time()) {
Session::clear();
return json(['code' => 401, 'message' => '会话已过期'], 401);
}
if (!$user) {
return json(['code' => 401, 'message' => '未登录'], 401);
}
return json(['code' => 200, 'data' => $user]);
}
public function logout()
{
Session::delete('user');
Session::delete('session_expire');
return json(['code' => 200, 'message' => '注销成功']);
}
}
令牌生命周期管理建议
前端实现逻辑(JavaScript示例)
// token-manager.js
class TokenManager {
constructor() {
this.accessToken = null;
this.refreshToken = null;
this.accessTokenExpire = 0;
}
// 保存令牌
setTokens(tokens) {
this.accessToken = tokens.access_token;
this.refreshToken = tokens.refresh_token;
this.accessTokenExpire = Date.now() + tokens.expires_in * 1000;
// 存储到localStorage
localStorage.setItem('access_token', tokens.access_token);
localStorage.setItem('refresh_token', tokens.refresh_token);
localStorage.setItem('token_expire', this.accessTokenExpire);
}
// 获取访问令牌
async getAccessToken() {
// 检查是否过期
if (Date.now() >= this.accessTokenExpire) {
// 尝试刷新
const refreshed = await this.refresh();
if (refreshed) {
return this.accessToken;
}
return null;
}
return this.accessToken;
}
// 刷新令牌
async refresh() {
try {
const response = await fetch('/refresh', {
method: 'POST',
body: JSON.stringify({
refresh_token: this.refreshToken
})
});
const data = await response.json();
if (data.code === 200) {
this.setTokens(data.data);
return true;
}
// 刷新失败,跳转到登录页
this.logout();
return false;
} catch (error) {
console.error('Token refresh failed:', error);
return false;
}
}
// 请求拦截
async request(url, options = {}) {
const token = await this.getAccessToken();
if (token) {
options.headers = {
...options.headers,
'Authorization': `Bearer ${token}`
};
}
return fetch(url, options);
}
// 注销
logout() {
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
localStorage.removeItem('token_expire');
window.location.href = '/login';
}
}
// 使用示例
const tokenManager = new TokenManager();
// 登录成功后
tokenManager.setTokens(loginResponse.data);
// 发起请求
tokenManager.request('/user/info')
.then(res => res.json())
.then(data => console.log(data));
Token过期策略建议
-
双Token方案:
- 访问令牌(Access Token):短期有效(如2小时)
- 刷新令牌(Refresh Token):长期有效(如7天)
-
主动续期策略:
- 在访问令牌即将过期时(如剩余10分钟)自动刷新
- 在API响应中携带令牌剩余时间
-
Token撤销机制:
- 用户修改密码后撤销所有Token
- 用户主动注销时加入黑名单
-
安全性增强:
- 使用HTTPS传输
- 存储时加密Token
- 限制刷新令牌的使用次数
这样既保证了系统的安全性,又提供了良好的用户体验,根据你的具体业务需求,可以选择相应的实现方案。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播