PHP JWT如何防篡改

wen PHP项目 3

本文目录导读:

PHP JWT如何防篡改

  1. 核心原理:签名验证
  2. 使用 PHP 原生实现(不依赖库)
  3. 使用知名库(推荐)
  4. 安全加固措施
  5. 验证流程总结
  6. 关键安全要点

在 PHP 中使用 JWT(JSON Web Token)时,防篡改的核心机制是数字签名,JWT 由三部分组成:Header.Payload.Signature,其中签名就是用来验证数据完整性和真实性的。

以下是在 PHP 中实现 JWT 防篡改的完整策略:

核心原理:签名验证

JWT 的签名生成算法(以 HS256 为例):

// 签名生成公式
$signature = hash_hmac('sha256', "$header.$payload", $secret_key, true);

关键点:任何对 Payload 的修改都会导致签名验证失败。

使用 PHP 原生实现(不依赖库)

1 生成 Token

class JwtHandler {
    private $secret_key;
    private $algorithm = 'HS256';
    public function __construct($secret_key) {
        $this->secret_key = $secret_key;
    }
    // 生成 JWT
    public function generateToken(array $payload, $expiry_hours = 24) {
        // 1. 创建 Header
        $header = [
            'alg' => $this->algorithm,
            'typ' => 'JWT'
        ];
        // 2. 创建 Payload(加入过期时间等)
        $payload['iat'] = time(); // 签发时间
        $payload['exp'] = time() + ($expiry_hours * 3600); // 过期时间
        $payload['jti'] = bin2hex(random_bytes(16)); // 唯一标识
        // 3. Base64Url 编码
        $header_encoded = $this->base64UrlEncode(json_encode($header));
        $payload_encoded = $this->base64UrlEncode(json_encode($payload));
        // 4. 生成签名
        $signature = $this->generateSignature($header_encoded, $payload_encoded);
        // 5. 组合 JWT
        return "$header_encoded.$payload_encoded.$signature";
    }
    // 验证 JWT
    public function verifyToken($token) {
        try {
            // 1. 拆分 JWT
            $parts = explode('.', $token);
            if (count($parts) !== 3) {
                throw new Exception('Invalid token structure');
            }
            [$header_encoded, $payload_encoded, $signature] = $parts;
            // 2. 重新计算签名并比较
            $expected_signature = $this->generateSignature($header_encoded, $payload_encoded);
            // 使用恒定时长比较,防止时序攻击
            if (!hash_equals($expected_signature, $signature)) {
                throw new Exception('Signature verification failed');
            }
            // 3. 解码并验证 Payload
            $payload = json_decode($this->base64UrlDecode($payload_encoded), true);
            // 4. 验证过期时间
            if (isset($payload['exp']) && $payload['exp'] < time()) {
                throw new Exception('Token has expired');
            }
            // 5. 验证签发时间(防止使用未来的 Token)
            if (isset($payload['iat']) && $payload['iat'] > time() + 300) {
                throw new Exception('Token issued in future');
            }
            return $payload;
        } catch (Exception $e) {
            error_log('JWT verification failed: ' . $e->getMessage());
            return null;
        }
    }
    // 生成签名
    private function generateSignature($header_encoded, $payload_encoded) {
        $data = "$header_encoded.$payload_encoded";
        $signature = hash_hmac('sha256', $data, $this->secret_key, true);
        return $this->base64UrlEncode($signature);
    }
    // Base64Url 编码
    private function base64UrlEncode($data) {
        return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
    }
    private function base64UrlDecode($data) {
        return base64_decode(strtr($data, '-_', '+/'));
    }
}

2 使用示例

// 初始化
$jwt = new JwtHandler('your-strong-secret-key-here');
// 生成 Token
$payload = [
    'user_id' => 123,
    'username' => 'example_user',
    'role' => 'admin'
];
$token = $jwt->generateToken($payload, 2); // 2小时过期
echo "Generated Token: $token\n";
// 验证 Token
$verified_payload = $jwt->verifyToken($token);
if ($verified_payload) {
    echo "Token valid. User ID: " . $verified_payload['user_id'];
} else {
    echo "Token invalid or expired";
}

使用知名库(推荐)

1 firebase/php-jwt 库

// 安装:composer require firebase/php-jwt
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
class TokenService {
    private $secret_key;
    private $algorithm = 'HS256';
    public function __construct() {
        $this->secret_key = getenv('JWT_SECRET_KEY'); // 从环境变量读取
    }
    public function generateToken(array $payload) {
        $issued_at = time();
        $expiration = $issued_at + 3600; // 1小时后过期
        $data = [
            'iat' => $issued_at,          // 签发时间
            'iss' => 'your-domain.com',   // 签发者
            'nbf' => $issued_at,          // 在此之前不可用
            'exp' => $expiration,         // 过期时间
            'data' => $payload            // 自定义数据
        ];
        return JWT::encode($data, $this->secret_key, $this->algorithm);
    }
    public function verifyToken($token) {
        try {
            $decoded = JWT::decode($token, new Key($this->secret_key, $this->algorithm));
            return (array) $decoded;
        } catch (\Exception $e) {
            error_log('Token verification failed: ' . $e->getMessage());
            return null;
        }
    }
}

2 使用 lcobucci/jwt 库(面向对象风格)

use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\Signer\Key\InMemory;
class JwtService {
    private $config;
    public function __construct() {
        $this->config = Configuration::forSymmetricSigner(
            new Sha256(),
            InMemory::plainText(getenv('JWT_SECRET_KEY'))
        );
    }
    public function generateToken(array $claims) {
        $now = new DateTimeImmutable();
        $token = $this->config->builder()
            ->issuedBy('your-domain.com')
            ->permittedFor('your-api.com')
            ->identifiedBy(bin2hex(random_bytes(16)))
            ->issuedAt($now)
            ->canOnlyBeUsedAfter($now)
            ->expiresAt($now->modify('+1 hour'))
            ->withClaim('user_id', $claims['user_id'])
            ->withClaim('role', $claims['role'])
            ->getToken($this->config->signer(), $this->config->signingKey());
        return $token->toString();
    }
    public function verifyToken($token_string) {
        try {
            $token = $this->config->parser()->parse($token_string);
            // 验证签名
            if (!$token->verify($this->config->signer(), $this->config->verificationKey())) {
                throw new \Exception('Invalid signature');
            }
            // 验证过期时间
            if ($token->isExpired(new DateTimeImmutable())) {
                throw new \Exception('Token expired');
            }
            return $token->claims()->all();
        } catch (\Exception $e) {
            error_log('JWT verification failed: ' . $e->getMessage());
            return null;
        }
    }
}

安全加固措施

1 密钥管理

// 使用环境变量,不要硬编码密钥
// .env 文件
JWT_SECRET_KEY=your-very-long-random-secret-key-here
// PHP 代码
$secret_key = getenv('JWT_SECRET_KEY');

2 防止重放攻击

class EnhancedJwtService {
    // 使用 Redis 存储已使用的 jti
    public function verifyTokenWithReplayProtection($token) {
        $payload = $this->verifyToken($token);
        if (!$payload) return null;
        $redis = new Redis();
        // 检查 jti 是否已被使用
        $key = "jwt:used:" . $payload['jti'];
        if ($redis->exists($key)) {
            return null; // 重放攻击检测
        }
        // 标记为已使用,设置与 Token 相同的过期时间
        $redis->setex($key, $payload['exp'] - time(), '1');
        return $payload;
    }
}

3 签名算法选择

// 推荐使用更安全的算法
$algorithms = [
    'HS256' => ['sha256', 64],  // HMAC-SHA256
    'HS384' => ['sha384', 96],  // HMAC-SHA384
    'HS512' => ['sha512', 128], // HMAC-SHA512
    'RS256' => 'RSA-SHA256',    // 非对称加密
];
// 生产环境中建议使用 RS256(非对称),私钥签名,公钥验证

验证流程总结

完整的防篡改验证流程:

function authenticateRequest($token) {
    // 1. 格式验证
    if (!preg_match('/^[a-zA-Z0-9-_]+\.[a-zA-Z0-9-_]+\.[a-zA-Z0-9-_]+$/', $token)) {
        return ['error' => 'Invalid token format'];
    }
    // 2. 签名验证
    $jwt_service = new JwtService();
    $payload = $jwt_service->verifyToken($token);
    if (!$payload) {
        return ['error' => 'Token signature invalid or expired'];
    }
    // 3. 权限检查
    if (!checkUserPermissions($payload['data']['role'])) {
        return ['error' => 'Insufficient permissions'];
    }
    // 4. 获取用户信息
    $user = getUserFromPayload($payload);
    return ['success' => true, 'user' => $user];
}

关键安全要点

  1. 永远不要在前端存储秘密密钥
  2. 使用 HTTPS 传输 Token
  3. 定期轮换密钥
  4. 设置合理的过期时间
  5. 避免在 Payload 中存储敏感信息
  6. 使用 hash_equals() 进行签名比较
  7. 验证 exp, iat, nbf 等时间声明

通过这些方法,可以确保 JWT 在网络传输中被篡改时能够被及时检测并拒绝。

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