本文目录导读:

我来详细介绍在PHP项目中实现Token管理的几种常见方案。
使用JWT(JSON Web Token)方案
1 安装JWT库
composer require firebase/php-jwt
2 创建Token管理类
<?php
namespace App\Services;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Firebase\JWT\ExpiredException;
class TokenService
{
private string $secretKey;
private string $algorithm;
private int $accessTokenExpiration;
private int $refreshTokenExpiration;
public function __construct()
{
$this->secretKey = getenv('JWT_SECRET') ?: 'your-secret-key-here';
$this->algorithm = 'HS256';
$this->accessTokenExpiration = 3600; // 1小时
$this->refreshTokenExpiration = 604800; // 7天
}
/**
* 生成访问令牌
*/
public function generateAccessToken(array $payload): string
{
$tokenPayload = [
'iss' => 'your-app-name', // 签发者
'iat' => time(), // 签发时间
'exp' => time() + $this->accessTokenExpiration, // 过期时间
'sub' => $payload['user_id'],
'role' => $payload['role'] ?? 'user',
'type' => 'access'
];
return JWT::encode($tokenPayload, $this->secretKey, $this->algorithm);
}
/**
* 生成刷新令牌
*/
public function generateRefreshToken(array $payload): string
{
$tokenPayload = [
'iss' => 'your-app-name',
'iat' => time(),
'exp' => time() + $this->refreshTokenExpiration,
'sub' => $payload['user_id'],
'token_id' => uniqid('refresh_', true),
'type' => 'refresh'
];
return JWT::encode($tokenPayload, $this->secretKey, $this->algorithm);
}
/**
* 验证令牌
*/
public function validateToken(string $token): ?object
{
try {
$decoded = JWT::decode($token, new Key($this->secretKey, $this->algorithm));
return $decoded;
} catch (ExpiredException $e) {
// 令牌过期
return null;
} catch (\Exception $e) {
// 其他验证错误
return null;
}
}
}
使用框架内置功能(以Laravel为例)
1 Laravel Sanctum(适合API)
<?php
namespace App\Http\Controllers\Api;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;
class AuthController extends Controller
{
/**
* 用户登录并生成令牌
*/
public function login(Request $request)
{
$request->validate([
'email' => 'required|email',
'password' => 'required',
]);
$user = User::where('email', $request->email)->first();
if (!$user || !Hash::check($request->password, $user->password)) {
throw ValidationException::withMessages([
'email' => ['提供的凭据不正确。'],
]);
}
// 创建令牌
$token = $user->createToken('api-token')->plainTextToken;
return response()->json([
'access_token' => $token,
'token_type' => 'Bearer',
]);
}
/**
* 验证用户令牌
*/
public function user(Request $request)
{
return $request->user();
}
/**
* 撤销当前令牌
*/
public function logout(Request $request)
{
$request->user()->currentAccessToken()->delete();
return response()->json(['message' => '已成功注销']);
}
}
2 路由配置
// routes/api.php
use App\Http\Controllers\Api\AuthController;
Route::post('/login', [AuthController::class, 'login']);
Route::middleware('auth:sanctum')->group(function () {
Route::get('/user', [AuthController::class, 'user']);
Route::post('/logout', [AuthController::class, 'logout']);
});
简单Token实现(不依赖库)
<?php
namespace App\Services;
class SimpleTokenService
{
private string $secretKey;
private int $expiration;
public function __construct()
{
$this->secretKey = 'your-secret-key';
$this->expiration = 3600; // 1小时
}
/**
* 生成简单令牌
*/
public function generateToken(array $data): string
{
$payload = base64_encode(json_encode([
'data' => $data,
'exp' => time() + $this->expiration,
'iat' => time()
]));
$signature = hash_hmac('sha256', $payload, $this->secretKey);
return base64_encode($payload . '.' . $signature);
}
/**
* 验证简单令牌
*/
public function validateToken(string $token): ?array
{
try {
$decoded = base64_decode($token);
$parts = explode('.', $decoded);
if (count($parts) !== 2) {
return null;
}
[$payload, $signature] = $parts;
// 验证签名
$expectedSignature = hash_hmac('sha256', $payload, $this->secretKey);
if (!hash_equals($expectedSignature, $signature)) {
return null;
}
// 解码载荷
$data = json_decode(base64_decode($payload), true);
// 检查过期时间
if ($data['exp'] < time()) {
return null;
}
return $data['data'];
} catch (\Exception $e) {
return null;
}
}
}
中间件实现
1 认证中间件
<?php
namespace App\Http\Middleware;
use App\Services\TokenService;
use Closure;
use Illuminate\Http\Request;
class TokenAuthMiddleware
{
private TokenService $tokenService;
public function __construct(TokenService $tokenService)
{
$this->tokenService = $tokenService;
}
/**
* 处理传入请求
*/
public function handle(Request $request, Closure $next)
{
$token = $request->bearerToken();
if (!$token) {
return response()->json([
'error' => '未提供令牌',
'message' => '请求已禁止'
], 401);
}
$decoded = $this->tokenService->validateToken($token);
if (!$decoded) {
return response()->json([
'error' => '无效或过期的令牌',
'message' => '请重新登录'
], 401);
}
// 将用户信息添加到请求中
$request->merge(['auth_user' => $decoded]);
return $next($request);
}
}
2 刷新令牌中间件
<?php
namespace App\Http\Middleware;
use App\Services\TokenService;
use Closure;
use Illuminate\Http\Request;
class TokenRefreshMiddleware
{
private TokenService $tokenService;
public function __construct(TokenService $tokenService)
{
$this->tokenService = $tokenService;
}
/**
* 检查令牌是否需要刷新
*/
public function handle(Request $request, Closure $next)
{
$response = $next($request);
// 如果令牌快要过期,生成新的令牌
if ($request->has('auth_user')) {
$user = $request->auth_user;
$tokenExp = $user->exp ?? 0;
$remainingTime = $tokenExp - time();
// 剩余时间少于10分钟时刷新令牌
if ($remainingTime < 600) {
$newToken = $this->tokenService->generateAccessToken([
'user_id' => $user->sub,
'role' => $user->role ?? 'user'
]);
$response->headers->set('X-New-Token', $newToken);
}
}
return $response;
}
}
完整示例:用户登录系统
1 用户模型
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
protected $fillable = [
'username',
'email',
'password',
'role'
];
protected $hidden = [
'password',
'remember_token',
];
}
2 认证控制器
<?php
namespace App\Http\Controllers\Api;
use App\Models\User;
use App\Services\TokenService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
class AuthController extends Controller
{
private TokenService $tokenService;
public function __construct(TokenService $tokenService)
{
$this->tokenService = $tokenService;
}
/**
* 用户注册
*/
public function register(Request $request)
{
$validated = $request->validate([
'username' => 'required|string|max:255|unique:users',
'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|string|min:8|confirmed',
]);
$user = User::create([
'username' => $validated['username'],
'email' => $validated['email'],
'password' => Hash::make($validated['password']),
]);
// 生成令牌
$accessToken = $this->tokenService->generateAccessToken([
'user_id' => $user->id,
'role' => 'user'
]);
$refreshToken = $this->tokenService->generateRefreshToken([
'user_id' => $user->id
]);
return response()->json([
'user' => $user,
'access_token' => $accessToken,
'refresh_token' => $refreshToken,
'token_type' => 'Bearer'
], 201);
}
/**
* 用户登录
*/
public function login(Request $request)
{
$validated = $request->validate([
'email' => 'required|email',
'password' => 'required',
]);
$user = User::where('email', $validated['email'])->first();
if (!$user || !Hash::check($validated['password'], $user->password)) {
return response()->json([
'error' => '认证失败',
'message' => '邮箱或密码错误'
], 401);
}
// 生成令牌
$accessToken = $this->tokenService->generateAccessToken([
'user_id' => $user->id,
'role' => $user->role
]);
$refreshToken = $this->tokenService->generateRefreshToken([
'user_id' => $user->id
]);
return response()->json([
'user' => $user,
'access_token' => $accessToken,
'refresh_token' => $refreshToken,
'token_type' => 'Bearer',
'expires_in' => 3600
]);
}
/**
* 刷新令牌
*/
public function refreshToken(Request $request)
{
$validated = $request->validate([
'refresh_token' => 'required|string'
]);
$decoded = $this->tokenService->validateToken($validated['refresh_token']);
if (!$decoded || $decoded->type !== 'refresh') {
return response()->json([
'error' => '无效的刷新令牌'
], 401);
}
// 生成新的访问令牌
$user = User::find($decoded->sub);
if (!$user) {
return response()->json([
'error' => '用户不存在'
], 401);
}
$newAccessToken = $this->tokenService->generateAccessToken([
'user_id' => $user->id,
'role' => $user->role
]);
return response()->json([
'access_token' => $newAccessToken,
'token_type' => 'Bearer',
'expires_in' => 3600
]);
}
}
最佳实践建议
安全性建议
- 使用HTTPS:始终在HTTPS下传输令牌
- 令牌有效期:访问令牌短时(15分钟-1小时),刷新令牌长时(7-30天)
- 密钥管理:密钥存储在环境变量中,定期更换
- 令牌撤销:实现令牌黑名单机制
性能优化
- 缓存令牌验证:使用Redis缓存已验证的令牌
- 数据库索引:为令牌相关字段创建索引
- 批量操作:需要撤销多个令牌时使用批量操作
常见问题处理
// 令牌失效处理
try {
$user = auth()->userOrFail();
} catch (\Tymon\JWTAuth\Exceptions\UserNotDefinedException $e) {
return response()->json(['error' => '用户未找到'], 401);
} catch (\Tymon\JWTAuth\Exceptions\TokenExpiredException $e) {
return response()->json(['error' => '令牌已过期'], 401);
} catch (\Tymon\JWTAuth\Exceptions\TokenInvalidException $e) {
return response()->json(['error' => '无效令牌'], 401);
}
选择哪种实现方式取决于你的项目需求:
- 小型项目:使用简单的自定义实现
- 大型企业应用:推荐使用JWT + 框架内置功能
- 微服务架构:考虑使用OAuth2.0协议