PHP 怎么PHP JWT 安全

wen PHP项目 3

本文目录导读:

PHP 怎么PHP JWT 安全

  1. 使用成熟的JWT库
  2. 基础安全的JWT实现示例
  3. 使用lcobucci/jwt(更安全更完整)
  4. 安全最佳实践
  5. 高级安全特性
  6. 重要安全提醒
  7. 完整的安全配置示例

我来详细讲解如何在PHP中安全地实现JWT(JSON Web Token)。

使用成熟的JWT库

推荐使用经过验证的JWT库,而不是自己实现:

composer require firebase/php-jwt

或者使用更完整的库:

composer require lcobucci/jwt

基础安全的JWT实现示例

使用firebase/php-jwt(简单示例)

<?php
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
class JwtService {
    private string $key;
    private string $issuer;
    private string $audience;
    public function __construct() {
        // 从环境变量获取密钥,不要硬编码
        $this->key = getenv('JWT_SECRET_KEY');
        $this->issuer = 'your-app-name';
        $this->audience = 'your-audience';
        if (empty($this->key) || strlen($this->key) < 32) {
            throw new Exception('JWT密钥必须至少32字节');
        }
    }
    public function generateToken(array $userData): string {
        $now = time();
        $payload = [
            'iss' => $this->issuer,        // 签发者
            'aud' => $this->audience,       // 受众
            'iat' => $now,                  // 签发时间
            'nbf' => $now,                  // 生效时间
            'exp' => $now + 3600,           // 过期时间(1小时)
            'jti' => bin2hex(random_bytes(16)), // 唯一标识
            'data' => [
                'user_id' => $userData['id'],
                'role' => $userData['role'] ?? 'user'
            ]
        ];
        return JWT::encode($payload, $this->key, 'HS256');
    }
    public function validateToken(string $token): array {
        try {
            // 设置解码后的验证参数
            $decoded = JWT::decode($token, new Key($this->key, 'HS256'));
            // 额外验证
            if ($decoded->iss !== $this->issuer) {
                throw new Exception('无效的签发者');
            }
            if ($decoded->aud !== $this->audience) {
                throw new Exception('无效的受众');
            }
            if ($decoded->exp < time()) {
                throw new Exception('Token已过期');
            }
            return (array)$decoded->data;
        } catch (\Firebase\JWT\ExpiredException $e) {
            throw new AuthException('Token已过期', 401);
        } catch (\Firebase\JWT\SignatureInvalidException $e) {
            throw new AuthException('签名无效', 401);
        } catch (\Exception $e) {
            throw new AuthException('无效的Token', 401);
        }
    }
}

使用lcobucci/jwt(更安全更完整)

<?php
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\Signer\Key\InMemory;
use Lcobucci\JWT\Validation\Constraint\SignedWith;
use Lcobucci\JWT\Validation\Constraint\ValidAt;
use Lcobucci\Clock\SystemClock;
class SecureJwtService {
    private Configuration $config;
    public function __construct() {
        $key = getenv('JWT_SECRET_KEY');
        $this->config = Configuration::forSymmetricSigner(
            new Sha256(),
            InMemory::plainText($key)
        );
        // 添加验证约束
        $this->config->setValidationConstraints(
            new SignedWith($this->config->signer(), $this->config->verificationKey()),
            new ValidAt(SystemClock::fromUTC())
        );
    }
    public function generateToken(string $userId, string $role): string {
        $now = new DateTimeImmutable();
        $token = $this->config->builder()
            // 设置header
            ->issuedBy('your-app-server')
            ->permittedFor('your-frontend-app')
            ->identifiedBy(bin2hex(random_bytes(16)), true)
            ->issuedAt($now)
            ->canOnlyBeUsedAfter($now)
            ->expiresAt($now->modify('+1 hour'))
            // 自定义Claims
            ->withClaim('uid', $userId)
            ->withClaim('role', $role)
            ->getToken($this->config->signer(), $this->config->signingKey());
        return $token->toString();
    }
    public function validateToken(string $tokenString): array {
        try {
            $token = $this->config->parser()->parse($tokenString);
            // 验证Token
            $constraints = $this->config->validationConstraints();
            $this->config->validator()->assert($token, ...$constraints);
            $claims = $token->claims();
            return [
                'user_id' => $claims->get('uid'),
                'role' => $claims->get('role'),
                'expires_at' => $claims->get('exp')
            ];
        } catch (\Exception $e) {
            throw new AuthException('Token验证失败: ' . $e->getMessage());
        }
    }
}

安全最佳实践

密钥管理

// .env 文件中存储(不要提交到版本控制)
JWT_SECRET_KEY=${2:your-very-secure-secret-key-at-least-32-chars}
// 密钥生成
// Linux/Mac: openssl rand -base64 32
// PHP: bin2hex(random_bytes(32))

JWT认证中间件示例(用于框架)

class JwtAuthMiddleware {
    private JwtService $jwtService;
    public function __construct() {
        $this->jwtService = new JwtService();
    }
    public function handle(Request $request): void {
        // 1. 从请求头获取Token
        $authorization = $request->getHeader('Authorization');
        if (preg_match('/Bearer\s+(.*)/', $authorization, $matches)) {
            $token = $matches[1];
            try {
                // 2. 验证Token
                $claims = $this->jwtService->validateToken($token);
                // 3. 将用户信息存入请求
                $request->setAttribute('user_id', $claims['user_id']);
                $request->setAttribute('role', $claims['role']);
            } catch (AuthException $e) {
                // 4. Token无效,返回401
                http_response_code(401);
                echo json_encode(['error' => $e->getMessage()]);
                exit;
            }
        } else {
            http_response_code(401);
            echo json_encode(['error' => '缺少Authorization头']);
            exit;
        }
    }
}

高级安全特性

刷新Token机制

class RefreshTokenService {
    public function generateRefreshToken(string $userId): string {
        return bin2hex(random_bytes(64)); // 生成随机刷新令牌
    }
    public function refreshAccessToken(string $refreshToken): array {
        // 验证刷新令牌(存储在数据库/Redis)
        $storedToken = $this->database->getRefreshToken($refreshToken);
        if (!$storedToken || $storedToken['expires_at'] < time()) {
            throw new AuthException('刷新令牌无效或已过期');
        }
        // 生成新的访问令牌
        $accessToken = $this->jwtService->generateToken(
            $storedToken['user_id'],
            $storedToken['role']
        );
        // 轮换刷新令牌(安全实践)
        $newRefreshToken = $this->generateRefreshToken($storedToken['user_id']);
        return [
            'access_token' => $accessToken,
            'refresh_token' => $newRefreshToken
        ];
    }
}

Token黑名单机制

class TokenBlackList {
    public function addToBlacklist(string $jti, int $expires_at): void {
        // 使用Redis存储
        Redis::set("blacklist:{$jti}", true, 'EX', $expires_at - time());
    }
    public function isBlacklisted(string $jti): bool {
        return Redis::exists("blacklist:{$jti}");
    }
}

重要安全提醒

  1. 永远不要将JWT存储在localStorage(易受XSS攻击),推荐使用HttpOnly Cookie
  2. 使用HTTPS传输,防止中间人攻击
  3. 设置合理的过期时间(访问令牌通常15分钟-1小时)
  4. 实现令牌轮换和黑名单机制
  5. 避免在JWT中存储敏感信息(可解密)
  6. 使用强加密算法(推荐HS256或RS256)
  7. 验证HS256签名时防止算法混淆攻击

完整的安全配置示例

final class JwtConfig {
    public const ALGORITHM = 'HS256';
    public const TOKEN_TTL = 3600; // 1小时
    public const REFRESH_TTL = 2592000; // 30天
    public const MAX_REFRESH_USE = 10; // 刷新令牌最大使用次数
    public static function getSecretKey(): string {
        $key = getenv('JWT_SECRET_KEY');
        if (!$key || strlen($key) < 32) {
            throw new RuntimeException('JWT密钥配置错误');
        }
        return $key;
    }
}

通过遵循这些安全实践,你可以构建一个安全可靠的JWT认证系统,记得定期更新密钥,并监控异常使用模式。

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