PHP 怎么PHP 时效令牌

wen PHP项目 2

本文目录导读:

PHP 怎么PHP 时效令牌

  1. 基于时间戳的令牌生成
  2. 使用 JWT(JSON Web Token)
  3. 基于 Redis 的令牌管理
  4. 带刷新机制的完整实现
  5. 实际应用示例
  6. 安全建议

我来为您详细介绍 PHP 中实现时效令牌的几种方法。

基于时间戳的令牌生成

基础实现

class TimeBasedToken {
    /**
     * 生成时效令牌
     * @param string $data 要加密的数据
     * @param int $expireTime 过期时间(秒)
     * @return string
     */
    public static function generate($data, $expireTime = 3600) {
        $expireTimestamp = time() + $expireTime;
        $payload = json_encode([
            'data' => $data,
            'exp' => $expireTimestamp,
            'iat' => time()
        ]);
        return base64_encode($payload) . '.' . 
               self::sign($payload);
    }
    /**
     * 验证令牌
     * @param string $token
     * @return array|false
     */
    public static function verify($token) {
        $parts = explode('.', $token);
        if (count($parts) !== 2) {
            return false;
        }
        $payload = base64_decode($parts[0]);
        $signature = $parts[1];
        // 验证签名
        if (!self::verifySignature($payload, $signature)) {
            return false;
        }
        $data = json_decode($payload, true);
        // 检查是否过期
        if ($data['exp'] < time()) {
            return false;
        }
        return $data;
    }
    private static function sign($payload) {
        $secret = 'your-secret-key'; // 使用环境变量存储
        return hash_hmac('sha256', $payload, $secret);
    }
    private static function verifySignature($payload, $signature) {
        return hash_equals(self::sign($payload), $signature);
    }
}
// 使用示例
$token = TimeBasedToken::generate('user_data', 3600); // 1小时过期
$result = TimeBasedToken::verify($token);

使用 JWT(JSON Web Token)

推荐使用 firebase/php-jwt 库:

// 安装:composer require firebase/php-jwt
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
class JwtToken {
    private static $key = 'your-secret-key'; // 应该从环境变量获取
    /**
     * 生成JWT令牌
     */
    public static function generate($userId, $expireHours = 24) {
        $payload = [
            'iss' => 'your-app',        // 签发者
            'iat' => time(),            // 签发时间
            'exp' => time() + ($expireHours * 3600), // 过期时间
            'user_id' => $userId,       // 自定义数据
            'jti' => uniqid('token_')   // 令牌ID,用于吊销
        ];
        return JWT::encode($payload, self::$key, 'HS256');
    }
    /**
     * 验证JWT令牌
     */
    public static function verify($token) {
        try {
            $decoded = JWT::decode($token, new Key(self::$key, 'HS256'));
            return (array) $decoded;
        } catch (\Exception $e) {
            // 令牌过期或无效
            return false;
        }
    }
}
// 使用示例
$token = JwtToken::generate(123, 48); // 48小时过期
$userData = JwtToken::verify($token);

基于 Redis 的令牌管理

class RedisTokenManager {
    private $redis;
    public function __construct() {
        $this->redis = new Redis();
        $this->redis->connect('127.0.0.1', 6379);
    }
    /**
     * 生成并存储令牌
     */
    public function generateToken($userId, $expireSeconds = 3600) {
        $token = bin2hex(random_bytes(32)); // 生成随机令牌
        $tokenData = [
            'user_id' => $userId,
            'created_at' => time(),
            'ip' => $_SERVER['REMOTE_ADDR'] ?? '',
            'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? ''
        ];
        // 存储到Redis,设置过期时间
        $this->redis->setex(
            "token:{$token}", 
            $expireSeconds, 
            json_encode($tokenData)
        );
        return $token;
    }
    /**
     * 验证令牌
     */
    public function verifyToken($token) {
        $tokenKey = "token:{$token}";
        if (!$this->redis->exists($tokenKey)) {
            return false;
        }
        $tokenData = json_decode($this->redis->get($tokenKey), true);
        // 可选:续期令牌
        $this->redis->expire($tokenKey, 3600); // 续期1小时
        return $tokenData;
    }
    /**
     * 吊销令牌
     */
    public function revokeToken($token) {
        $this->redis->del("token:{$token}");
    }
    /**
     * 清理过期令牌
     */
    public function cleanExpiredTokens() {
        // Redis自动处理过期键,无需手动清理
    }
}

带刷新机制的完整实现

class TokenAuth {
    private $secretKey;
    private $refreshExpire = 30 * 24 * 3600; // 30天
    private $accessExpire = 3600;             // 1小时
    public function __construct($secretKey) {
        $this->secretKey = $secretKey;
    }
    /**
     * 生成访问令牌和刷新令牌
     */
    public function generateTokens($userId) {
        $accessToken = $this->generateAccessToken($userId);
        $refreshToken = $this->generateRefreshToken();
        return [
            'access_token' => $accessToken,
            'refresh_token' => $refreshToken,
            'expires_in' => $this->accessExpire
        ];
    }
    /**
     * 刷新访问令牌
     */
    public function refreshAccessToken($refreshToken) {
        // 验证刷新令牌
        $refreshData = $this->verifyRefreshToken($refreshToken);
        if (!$refreshData) {
            return false;
        }
        // 生成新的访问令牌
        $newAccessToken = $this->generateAccessToken($refreshData['user_id']);
        return [
            'access_token' => $newAccessToken,
            'expires_in' => $this->accessExpire
        ];
    }
    private function generateAccessToken($userId) {
        $header = base64_encode(json_encode(['typ' => 'JWT', 'alg' => 'HS256']));
        $payload = base64_encode(json_encode([
            'user_id' => $userId,
            'exp' => time() + $this->accessExpire,
            'iat' => time(),
            'type' => 'access'
        ]));
        $signature = hash_hmac('sha256', "$header.$payload", $this->secretKey);
        return "$header.$payload.$signature";
    }
    private function generateRefreshToken() {
        return bin2hex(random_bytes(32));
    }
    private function verifyRefreshToken($refreshToken) {
        // 从数据库或Redis验证刷新令牌
        // 这里需要实现数据库查询逻辑
        return ['user_id' => 123]; // 示例返回值
    }
}

实际应用示例

// 用户登录
class AuthController {
    private $tokenAuth;
    private $db;
    public function login($username, $password) {
        // 验证用户凭据
        $user = $this->db->query("SELECT * FROM users WHERE username = ?", [$username]);
        if (!password_verify($password, $user['password'])) {
            return ['error' => '无效的凭据'];
        }
        // 生成令牌
        $tokens = $this->tokenAuth->generateTokens($user['id']);
        // 存储刷新令牌到数据库
        $this->db->query(
            "INSERT INTO refresh_tokens (user_id, token, expires_at) VALUES (?, ?, ?)",
            [$user['id'], $tokens['refresh_token'], date('Y-m-d H:i:s', time() + 30*24*3600)]
        );
        return [
            'success' => true,
            'tokens' => $tokens
        ];
    }
    // 验证请求中的访问令牌
    public function authenticate() {
        $authHeader = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
        if (strpos($authHeader, 'Bearer ') !== 0) {
            http_response_code(401);
            echo json_encode(['error' => '未授权']);
            exit;
        }
        $token = substr($authHeader, 7);
        $userData = $this->tokenAuth->verifyAccessToken($token);
        if (!$userData) {
            http_response_code(401);
            echo json_encode(['error' => '令牌过期或无效']);
            exit;
        }
        return $userData;
    }
}

安全建议

  1. 密钥管理:永远不要在代码中硬编码密钥,使用环境变量
  2. HTTPS:始终使用HTTPS传输令牌
  3. 令牌存储:前端使用HttpOnly Cookie或localStorage
  4. 验证所有请求:在中间件中统一验证令牌
  5. 限制令牌范围:实现基于角色的访问控制
// 示例:中间件验证
class AuthMiddleware {
    public function handle($request, $next) {
        $token = $request->bearerToken();
        try {
            $payload = JWT::decode($token, $this->key, ['HS256']);
            // 将用户信息存储到请求中
            $request->setUser($payload->user_id);
            $request->setRole($payload->role);
            return $next($request);
        } catch (ExpiredException $e) {
            return response()->json(['error' => '令牌已过期'], 401);
        } catch (\Exception $e) {
            return response()->json(['error' => '无效令牌'], 401);
        }
    }
}

选择合适的实现方式取决于您的具体需求:

  • 简单需求:使用基于时间戳的简单令牌
  • 标准API:使用JWT
  • 高安全性:结合JWT和Redis存储
  • 需要刷新机制:实现访问令牌+刷新令牌模式

抱歉,评论功能暂时关闭!