本文目录导读:

在 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];
}
关键安全要点
- 永远不要在前端存储秘密密钥
- 使用 HTTPS 传输 Token
- 定期轮换密钥
- 设置合理的过期时间
- 避免在 Payload 中存储敏感信息
- 使用
hash_equals()进行签名比较 - 验证
exp,iat,nbf等时间声明
通过这些方法,可以确保 JWT 在网络传输中被篡改时能够被及时检测并拒绝。