认证系统

最后更新: 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(签名)
  • JWT 的优势

  • 无状态:服务器不需要存储会话信息
  • 跨域支持:可以在不同域名间使用
  • 可扩展:可以在 Payload 中存储自定义数据
  • 安全性:使用签名防止篡改
  • 生成 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 时会:

  • 验证 Token 签名(即使已过期)
  • 提取原始 Payload
  • 生成新的 Token(使用新的过期时间)
  • 如果启用 SSO,使旧 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 后:

  • 用户登录时生成 Token 并存储
  • 如果同一用户再次登录,旧 Token 会被标记为失效
  • 验证 Token 时检查是否已被标记为失效
  • 失效的 Token 无法通过验证
  • 使用 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
    

    安全建议

  • 使用强密钥:密钥应该足够长且随机
  • 使用环境变量:不要将密钥硬编码在代码中
  • HTTPS:在生产环境使用 HTTPS 传输 Token
  • 合理设置有效期:根据业务需求设置合适的 TTL
  • 启用 SSO:如果需要单点登录功能,启用 SSO
  • 完整示例

    登录接口

    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 三种存储方式。

    相关文档

  • 路由系统 - 路由和中间件
  • 配置管理 - 配置系统
  • 安全指南 - 安全最佳实践