Appearance
JWT 开发指南
概述
DSPlatform 使用 JWT (JSON Web Token) 进行用户身份认证和授权。JWT 是一种无状态的认证机制,通过签名确保 token 的完整性和安全性。
技术架构
核心组件
- JwtToken: JWT token 生成和验证类
- TokenCache: Token 版本控制和失效管理类
- Firebase JWT: 第三方 JWT 库,用于 token 的编码和解码
依赖库
json
{
"firebase/php-jwt": "^6.0"
}
JWT 实现
JwtToken 类
构造函数
php
// app/deshang/utils/JwtToken.php
class JwtToken
{
protected $jwt_key;
public function __construct()
{
$this->jwt_key = env('JWT_SECRET_KEY');
if (empty($this->jwt_key)) {
throw new \Exception('JWT密钥未配置,请检查.env文件中的JWT_SECRET设置');
}
}
}
生成 Token
php
/**
* 生成新的令牌(自动添加版本控制)
*
* @param array $payload_data Token载荷数据
* @param int $expire 过期时间(秒)
* @return array
*/
public function generateToken($payload_data, $expire)
{
// 如果payload包含role和user_id,自动添加版本号
if (isset($payload_data['role']) && isset($payload_data['user_id'])) {
$payload_data['token_version'] = (new TokenCache())->getCurrentVersion(
$payload_data['role'],
$payload_data['user_id']
);
}
$payload = [
'iss' => request()->host(true), // 签发者
'aud' => request()->host(true), // 接收者
'iat' => time(), // 签发时间
'nbf' => time(), // 在此之前不可用
'exp' => time() + $expire, // 过期时间
'data' => $payload_data,
];
$token = JWT::encode($payload, $this->jwt_key, 'HS256');
return [
'token' => $token,
'exp' => $payload['exp'],
];
}
验证 Token
php
/**
* 验证令牌(自动检查版本)
*
* @param string $token JWT Token
* @return array|false
*/
public function validateToken($token)
{
try {
$decoded = JWT::decode($token, new Key($this->jwt_key, 'HS256'));
$token_data = (array) $decoded->data;
// 如果token包含版本信息,自动验证版本
if (isset($token_data['role']) && isset($token_data['user_id']) && isset($token_data['token_version'])) {
if (!(new TokenCache())->validateTokenVersion(
$token_data['role'],
$token_data['user_id'],
$token_data['token_version']
)) {
return false; // Token版本不匹配,已失效
}
}
return $token_data;
} catch (\Exception $e) {
return false;
}
}
刷新 Token
php
/**
* 刷新令牌
*
* @param string $token JWT Token
* @return string|false
*/
public function refreshToken($token)
{
$jwt_expire = 3600 * 5; // 5小时
try {
$decoded = JWT::decode($token, new Key($this->jwt_key, 'HS256'));
$payload = (array) $decoded;
$payload['iat'] = time();
$payload['nbf'] = time();
$payload['exp'] = time() + $jwt_expire;
return JWT::encode($payload, $this->jwt_key, 'HS256');
} catch (\Exception $e) {
return false;
}
}
TokenCache 类
版本验证
php
// app/deshang/utils/TokenCache.php
class TokenCache
{
const TOKEN_VERSION_PREFIX = 'token_version_';
const DEFAULT_EXPIRE = 3600 * 24 * 7; // 7天
/**
* 验证Token版本是否有效
*/
public function validateTokenVersion(string $role, int $user_id, int $token_version): bool
{
$cache_key = self::TOKEN_VERSION_PREFIX . "{$role}_{$user_id}";
$current_version = CacheUtil::get($cache_key, 1);
return $current_version == $token_version;
}
/**
* 使所有Token失效(递增版本号)
*/
public function invalidateToken(string $role, int $user_id): void
{
$cache_key = self::TOKEN_VERSION_PREFIX . "{$role}_{$user_id}";
$current_version = CacheUtil::get($cache_key, 1);
$new_version = $current_version + 1;
CacheUtil::set($cache_key, $new_version, self::DEFAULT_EXPIRE);
}
/**
* 获取当前Token版本号
*/
public function getCurrentVersion(string $role, int $user_id): int
{
$cache_key = self::TOKEN_VERSION_PREFIX . "{$role}_{$user_id}";
return CacheUtil::get($cache_key, 1);
}
}
Token 类型
Access Token
用途: 用于 API 访问认证
有效期: 1小时
载荷内容: 用户详细信息
用户端 Access Token
php
$access_payload_data = [
'role' => 'user',
'user_id' => $user_info['id'],
'username' => $user_info['username'],
'user_is_enabled' => $user_info['is_enabled'],
'merchant_id' => $user_info['merchant']['id'] ?? 0,
'merchant_is_enabled' => $user_info['merchant']['is_enabled'] ?? 0,
'rider_id' => $user_info['rider']['id'] ?? 0,
'rider_is_enabled' => $user_info['rider']['is_enabled'] ?? 0,
'technician_id' => $user_info['technician']['id'] ?? 0,
'technician_is_enabled' => $user_info['technician']['is_enabled'] ?? 0,
'blogger_id' => $user_info['blogger']['id'] ?? 0,
'blogger_is_enabled' => $user_info['blogger']['is_enabled'] ?? 0,
];
管理员端 Access Token
php
$access_payload_data = [
'role' => 'admin',
'user_id' => $adminInfo['id'],
'username' => $adminInfo['username'],
'admin_is_super' => $adminInfo['is_super'],
];
Refresh Token
用途: 用于刷新 Access Token
有效期: 7天
载荷内容: 基础用户信息
php
$refresh_payload_data = [
'role' => 'user', // 或 'admin'
'user_id' => $user_info['id'],
];
认证流程
登录流程
用户登录验证
- 验证用户名/密码或手机号/验证码
- 检查用户状态和权限
生成 Token
- 生成 Access Token(1小时有效期)
- 生成 Refresh Token(7天有效期)
- 自动添加版本号
返回 Token 信息
php$data = [ 'access_token' => $access_token['token'], 'access_expires_time' => $access_token['exp'], 'refresh_token' => $refresh_token['token'], 'refresh_expires_time' => $refresh_token['exp'], 'userinfo' => $user_info ];
Token 验证流程
提取 Token
- 从请求头
access-token
获取 token
- 从请求头
验证 Token
- 验证签名和过期时间
- 检查版本号是否匹配
注入用户信息
- 将用户信息注入到请求对象中
Token 刷新流程
验证 Refresh Token
- 检查 refresh token 的有效性
生成新的 Access Token
- 使用 refresh token 中的用户信息
- 生成新的 access token
返回新 Token
- 返回新的 access token 和过期时间
中间件集成
用户认证中间件
php
// app/api/middleware/UserAuthorizeToken.php
class UserAuthorizeToken
{
public function handle(Request $request, \Closure $next)
{
$token = $request->header('access-token');
if (strlen($token ?? '') > 0) {
$token_info = (new JwtToken())->validateToken($token);
if ($token_info == false) {
return ds_json_error('登录状态已失效,请重新登录', [], 10001, 401);
}
if ($token_info['role'] != 'user') {
return ds_json_error('身份验证失败,请重新登录', [], 10001, 403);
}
// 设置用户信息到请求对象
$request->user_id = $token_info['user_id'] ?? 0;
$request->username = $token_info['username'] ?? '';
$request->user_is_enabled = $token_info['user_is_enabled'] ?? 0;
$request->merchant_id = $token_info['merchant_id'] ?? 0;
$request->merchant_is_enabled = $token_info['merchant_is_enabled'] ?? 0;
$request->rider_id = $token_info['rider_id'] ?? 0;
$request->rider_is_enabled = $token_info['rider_is_enabled'] ?? 0;
$request->technician_id = $token_info['technician_id'] ?? 0;
$request->technician_is_enabled = $token_info['technician_is_enabled'] ?? 0;
$request->blogger_id = $token_info['blogger_id'] ?? 0;
$request->blogger_is_enabled = $token_info['blogger_is_enabled'] ?? 0;
}
return $next($request);
}
}
管理员认证中间件
php
// app/adminapi/middleware/AdminAuthorizeToken.php
class AdminAuthorizeToken
{
public function handle(Request $request, \Closure $next)
{
$token = $request->header('access-token');
if (strlen($token ?? '') > 0) {
$token_info = (new JwtToken())->validateToken($token);
if ($token_info == false) {
return ds_json_error('登录状态已失效,请重新登录', [], 10001, 401);
}
if ($token_info['role'] != 'admin') {
return ds_json_error('身份验证失败,请重新登录', [], 10001, 403);
}
// 设置管理员信息到请求对象
$request->admin_id = $token_info['user_id'] ?? 0;
$request->username = $token_info['username'] ?? '';
$request->admin_is_super = $token_info['admin_is_super'] ?? 0;
}
return $next($request);
}
}
配置说明
环境变量
env
# JWT 密钥(必须配置)
JWT_SECRET_KEY=your-secret-key-here
安全建议
密钥管理
- 使用强随机密钥
- 定期更换密钥
- 不同环境使用不同密钥
Token 安全
- 使用 HTTPS 传输
- 设置合理的过期时间
- 实现版本控制机制
版本控制
- 支持强制失效所有 token
- 防止 token 重放攻击
- 支持单点登录控制
使用示例
生成 Token
php
use app\deshang\utils\JwtToken;
$jwt = new JwtToken();
// 生成用户 token
$user_payload = [
'role' => 'user',
'user_id' => 123,
'username' => 'testuser',
'user_is_enabled' => 1,
];
$token_result = $jwt->generateToken($user_payload, 3600);
echo $token_result['token']; // JWT token
echo $token_result['exp']; // 过期时间戳
验证 Token
php
use app\deshang\utils\JwtToken;
$jwt = new JwtToken();
$token_info = $jwt->validateToken($token);
if ($token_info !== false) {
echo "用户ID: " . $token_info['user_id'];
echo "用户名: " . $token_info['username'];
echo "角色: " . $token_info['role'];
} else {
echo "Token 无效";
}
失效 Token
php
use app\deshang\utils\TokenCache;
$tokenCache = new TokenCache();
$tokenCache->invalidateToken('user', 123); // 使用户ID为123的所有token失效
错误处理
常见错误
Token 过期
- 错误码: 401
- 处理: 使用 refresh token 刷新
Token 无效
- 错误码: 401
- 处理: 重新登录
权限不足
- 错误码: 403
- 处理: 检查用户角色和权限
版本不匹配
- 错误码: 401
- 处理: 重新登录
错误响应格式
php
// Token 失效
return ds_json_error('登录状态已失效,请重新登录', [], 10001, 401);
// 权限不足
return ds_json_error('身份验证失败,请重新登录', [], 10001, 403);
最佳实践
1. Token 存储
- 前端: 存储在 localStorage 或 sessionStorage
- 后端: 不存储 token,只验证签名
2. 安全传输
- 使用 HTTPS 协议
- 在请求头中传递 token
- 避免在 URL 中传递敏感信息
3. 过期处理
- Access Token 短期有效(1小时)
- Refresh Token 长期有效(7天)
- 实现自动刷新机制
4. 版本控制
- 支持强制失效
- 防止 token 重放
- 支持单点登录
5. 监控和日志
- 记录 token 生成和验证日志
- 监控异常访问
- 统计 token 使用情况
最后更新:2024-01-20
维护者:DSPlatform技术团队