- JWT 基础
- 什么是 JWT?
- JWT 的优势
- 生成 Token
- 基本用法
- 使用配置
- 自定义有效期
- 添加标准声明
- 验证 Token
- 基本验证
- 验证结果
- 错误处理
- 刷新 Token
- 刷新过期 Token
- 刷新逻辑
- JWT 中间件
- 使用中间件
- 在控制器中获取用户信息
- 自定义中间件
- 单点登录(SSO)
- 启用 SSO
- SSO 工作原理
- 使用 SSO
- 登出(使 Token 失效)
- 配置说明
- 配置文件
- 环境变量
- 安全建议
- 完整示例
- 登录接口
- 受保护的路由
- 获取当前用户
- 最佳实践
- 1. Token 存储
- 2. Token 传输
- 3. 错误处理
- 4. 刷新策略
- 5. 多设备登录
- 常见问题
- Q: Token 过期后如何处理?
- Q: 如何实现登出?
- Q: Token 应该存储在哪里?
- Q: 如何实现 Token 刷新?
- Q: SSO 支持哪些存储方式?
- 相关文档
认证系统
最后更新: 2026-01-27 11:02:47认证系统
Unicode Framework 提供了完整的 JWT(JSON Web Token)认证系统,支持 Token 生成、验证、刷新和单点登录(SSO)。
JWT 基础
什么是 JWT?
JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在各方之间安全地传输信息。JWT 由三部分组成:
- Header(头部)
- Payload(载荷)
- Signature(签名)
- 无状态:服务器不需要存储会话信息
- 跨域支持:可以在不同域名间使用
- 可扩展:可以在 Payload 中存储自定义数据
- 安全性:使用签名防止篡改
JWT 的优势
生成 Token
基本用法
use Unicode\Framework\Auth\JWT;
// 创建 JWT 实例
$jwt = new JWT(
secret: 'your-secret-key',
algorithm: 'HS256',
ttl: 3600, // Token 有效期(秒)
);
// 生成 Token
$token = $jwt->encode([
'user_id' => 1,
'username' => 'admin',
'email' => 'admin@example.com',
]);
// 返回给客户端
return ['token' => $token];
使用配置
use Unicode\Framework\Config\Config;
use Unicode\Framework\Auth\JWT;
$config = Config::getInstance();
// 从配置读取密钥和设置
$secret = $config->get('auth.jwt.secret');
$algorithm = $config->get('auth.jwt.algorithm', 'HS256');
$ttl = $config->get('auth.jwt.ttl', 3600);
$jwt = new JWT($secret, $algorithm, $ttl);
$token = $jwt->encode(['user_id' => 1]);
自定义有效期
// 使用默认有效期
$token = $jwt->encode(['user_id' => 1]);
// 自定义有效期(秒)
$token = $jwt->encode(['user_id' => 1], ttl: 7200); // 2小时
添加标准声明
$jwt = new JWT(
secret: 'secret',
algorithm: 'HS256',
ttl: 3600,
issuer: 'my-app', // 签发者
audience: 'my-users', // 受众
);
$token = $jwt->encode(['user_id' => 1]);
// Token 中会包含 iss 和 aud 声明
验证 Token
基本验证
use Unicode\Framework\Auth\JWT;
$jwt = new JWT('your-secret-key');
try {
$payload = $jwt->decode($token);
// 获取用户信息
$userId = $payload['user_id'];
$username = $payload['username'];
} catch (\Exception $e) {
// Token 无效或已过期
return ['error' => 'Invalid token'];
}
验证结果
decode() 方法返回的 Payload 包含:
user_id, username)iat - 签发时间
- exp - 过期时间
- nbf - 生效时间
- iss - 签发者(如果设置)
- aud - 受众(如果设置)
错误处理
use Unicode\Framework\Auth\JWT;
$jwt = new JWT('your-secret-key');
try {
$payload = $jwt->decode($token);
} catch (\Unicode\Framework\Exception\TokenExpiredException $e) {
// Token 已过期
return ['error' => 'Token expired'];
} catch (\Unicode\Framework\Exception\InvalidTokenException $e) {
// Token 无效
return ['error' => 'Invalid token'];
} catch (\Exception $e) {
// 其他错误
return ['error' => $e->getMessage()];
}
刷新 Token
刷新过期 Token
use Unicode\Framework\Auth\JWT;
$jwt = new JWT('your-secret-key');
try {
// 尝试刷新 Token(即使已过期)
$newToken = $jwt->refresh($token);
return ['token' => $newToken];
} catch (\Exception $e) {
return ['error' => 'Cannot refresh token'];
}
刷新逻辑
刷新 Token 时会:
JWT 中间件
使用中间件
在路由中使用 JWT 中间件:
use Unicode\Framework\Route\Route;
use Unicode\Framework\Auth\Middleware\JwtMiddleware;
// 单个路由
Route::get('/profile', [UserController::class, 'profile'], 'profile', [
JwtMiddleware::class
]);
// 路由组
Route::middleware([JwtMiddleware::class], function() {
Route::get('/profile', [UserController::class, 'profile']);
Route::put('/profile', [UserController::class, 'updateProfile']);
});
在控制器中获取用户信息
namespace App\Index\Controller;
use Unicode\Framework\Http\Request;
class UserController
{
public function profile(Request $request): array
{
// 从请求属性中获取 JWT Payload
$payload = $request->getAttribute('jwt_payload');
$userId = $payload['user_id'] ?? null;
$username = $payload['username'] ?? null;
return [
'user_id' => $userId,
'username' => $username,
];
}
}
自定义中间件
namespace App\Middleware;
use Unicode\Framework\Interfaces\MiddlewareInterface;
use Unicode\Framework\Http\Request;
use Unicode\Framework\Http\Response;
class CustomJwtMiddleware implements MiddlewareInterface
{
public function handle(Request $request, callable $next): Response
{
$token = $request->getHeader('Authorization');
if (empty($token)) {
return Response::json(['error' => 'Unauthorized'], 401);
}
// 验证 Token
// ...
return $next($request);
}
}
单点登录(SSO)
启用 SSO
在配置文件中启用 SSO:
// config/auth.php
return [
'jwt' => [
'secret' => 'your-secret-key',
'algorithm' => 'HS256',
'ttl' => 3600,
'sso' => [
'enabled' => true,
'storage' => 'redis', // 或 'file', 'database'
],
],
];
SSO 工作原理
启用 SSO 后:
使用 SSO
use Unicode\Framework\Config\Config;
use Unicode\Framework\Auth\JWT;
use Unicode\Framework\Auth\JwtSSO;
$config = Config::getInstance();
// 创建 JWT 实例
$jwt = new JWT(
secret: $config->get('auth.jwt.secret'),
algorithm: $config->get('auth.jwt.algorithm', 'HS256'),
ttl: $config->get('auth.jwt.ttl', 3600),
);
// 生成 Token(启用 SSO)
$token = $jwt->encode(
['user_id' => 1],
ttl: null,
config: $config // 传入 config 以启用 SSO
);
// 验证 Token(自动检查是否失效)
try {
$payload = $jwt->decode($token, config: $config);
} catch (\Exception $e) {
// Token 无效或已被标记为失效
}
登出(使 Token 失效)
use Unicode\Framework\Auth\JwtSSO;
// 使指定用户的 Token 失效
JwtSSO::invalidateUser($config, $userId);
// 使指定 Token 失效
JwtSSO::invalidateToken($config, $token);
配置说明
配置文件
config/auth.php:
return [
'jwt' => [
// 密钥(必须设置,建议使用环境变量)
'secret' => env('JWT_SECRET', 'your-secret-key'),
// 算法(HS256, HS384, HS512)
'algorithm' => 'HS256',
// Token 有效期(秒)
'ttl' => 3600,
// 签发者(可选)
'issuer' => null,
// 受众(可选)
'audience' => null,
// SSO 配置
'sso' => [
'enabled' => false,
'storage' => 'file', // file, redis, database
],
],
];
环境变量
在 .env 文件中设置:
JWT_SECRET=your-very-secret-key-here
JWT_ALGORITHM=HS256
JWT_TTL=3600
安全建议
完整示例
登录接口
namespace App\Index\Controller;
use Unicode\Framework\Http\Request;
use Unicode\Framework\Auth\JWT;
use Unicode\Framework\Config\Config;
class AuthController
{
public function login(Request $request): array
{
$email = $request->post('email');
$password = $request->post('password');
// 验证用户(示例)
$user = db('users')
->where('email', $email)
->first();
if (!$user || !password_verify($password, $user['password'])) {
return Response::json(['error' => 'Invalid credentials'], 401);
}
// 生成 Token
$config = Config::getInstance();
$jwt = new JWT(
secret: $config->get('auth.jwt.secret'),
algorithm: $config->get('auth.jwt.algorithm', 'HS256'),
ttl: $config->get('auth.jwt.ttl', 3600),
);
$token = $jwt->encode(
[
'user_id' => $user['id'],
'email' => $user['email'],
],
config: $config
);
return [
'token' => $token,
'user' => [
'id' => $user['id'],
'email' => $user['email'],
],
];
}
}
受保护的路由
// route.php
use Unicode\Framework\Route\Route;
use Unicode\Framework\Auth\Middleware\JwtMiddleware;
Route::post('/auth/login', [AuthController::class, 'login']);
Route::middleware([JwtMiddleware::class], function() {
Route::get('/profile', [UserController::class, 'profile']);
Route::put('/profile', [UserController::class, 'updateProfile']);
});
获取当前用户
class UserController
{
public function profile(Request $request): array
{
$payload = $request->getAttribute('jwt_payload');
$userId = $payload['user_id'] ?? null;
if (!$userId) {
return Response::json(['error' => 'Unauthorized'], 401);
}
$user = db('users')->where('id', $userId)->first();
return ['user' => $user];
}
}
最佳实践
1. Token 存储
// ✅ 推荐:存储在 HttpOnly Cookie 中
setcookie('token', $token, [
'httponly' => true,
'secure' => true,
'samesite' => 'Strict',
]);
// ✅ 也可以:存储在 localStorage(前端)
// 注意:需要手动在请求头中发送
2. Token 传输
// 在请求头中发送 Token
// Authorization: Bearer {token}
$headers = [
'Authorization' => 'Bearer ' . $token,
];
3. 错误处理
try {
$payload = $jwt->decode($token);
} catch (\Unicode\Framework\Exception\TokenExpiredException $e) {
// Token 过期,尝试刷新
$newToken = $jwt->refresh($token);
return ['token' => $newToken];
} catch (\Exception $e) {
// 其他错误,要求重新登录
return Response::json(['error' => 'Please login again'], 401);
}
4. 刷新策略
// 在 Token 即将过期时自动刷新
$payload = $jwt->decode($token);
$exp = $payload['exp'] ?? 0;
$now = time();
// 如果 Token 在 5 分钟内过期,自动刷新
if ($exp - $now < 300) {
$newToken = $jwt->refresh($token);
return ['token' => $newToken];
}
5. 多设备登录
// 使用 SSO 限制单设备登录
$jwt = new JWT($secret);
$token = $jwt->encode(['user_id' => $userId], config: $config);
// 用户在新设备登录时,旧设备的 Token 会自动失效
常见问题
Q: Token 过期后如何处理?
A: 使用 refresh() 方法刷新 Token,或要求用户重新登录。
Q: 如何实现登出?
A: 如果启用 SSO,使用 JwtSSO::invalidateToken() 使 Token 失效。否则,客户端删除 Token 即可。
Q: Token 应该存储在哪里?
A: 推荐使用 HttpOnly Cookie(更安全),也可以使用 localStorage(需要手动处理)。
Q: 如何实现 Token 刷新?
A: 提供刷新接口,使用 refresh() 方法生成新 Token。
Q: SSO 支持哪些存储方式?
A: 支持 file、redis、database 三种存储方式。