PHP原生JWT深度解析:从算法原理到安全实践
什么是JWT?
JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在各方之间安全地传输信息作为JSON对象。它由三部分组成:
头部(Header)- 包含令牌类型和使用的哈希算法 有效载荷(Payload)- 包含声明(用户信息和其他数据) 签名(Signature)- 用于验证消息在传输过程中未被更改
准备工作
在开始之前,确保您的PHP环境满足以下要求:
PHP 7.2或更高版本 启用的OpenSSL扩展(用于签名和验证) 基本的PHP和HTTP协议知识
第一步:创建JWT类
首先,我们创建一个处理JWT生成和验证的类:
class JWT {
private $secretKey;
publicfunction __construct($secretKey) {
$this->secretKey = $secretKey;
}
// 生成JWT
publicfunction encode(array $payload, $expiry = 3600): string {
$header = json_encode([
'typ' => 'JWT',
'alg' => 'HS256'
]);
$now = time();
$payload['iat'] = $now;
$payload['exp'] = $now + $expiry;
$payload = json_encode($payload);
$base64UrlHeader = $this->base64UrlEncode($header);
$base64UrlPayload = $this->base64UrlEncode($payload);
$signature = hash_hmac('sha256', $base64UrlHeader . "." . $base64UrlPayload, $this->secretKey, true);
$base64UrlSignature = $this->base64UrlEncode($signature);
return $base64UrlHeader . "." . $base64UrlPayload . "." . $base64UrlSignature;
}
// 验证并解码JWT
publicfunction decode(string $token): ?array {
$parts = explode('.', $token);
if (count($parts) !== 3) {
returnnull;
}
list($base64UrlHeader, $base64UrlPayload, $base64UrlSignature) = $parts;
$signature = $this->base64UrlDecode($base64UrlSignature);
$expectedSignature = hash_hmac('sha256', $base64UrlHeader . "." . $base64UrlPayload, $this->secretKey, true);
if (!hash_equals($signature, $expectedSignature)) {
returnnull;
}
$payload = json_decode($this->base64UrlDecode($base64UrlPayload), true);
if (isset($payload['exp']) && $payload['exp'] < time()) {
returnnull;
}
return $payload;
}
privatefunction base64UrlEncode(string $data): string {
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
privatefunction base64UrlDecode(string $data): string {
return base64_decode(strtr($data, '-_', '+/'));
}
}
第二步:用户认证和令牌发放
创建一个简单的用户认证系统来发放JWT:
// 模拟用户数据库
$users = [
'user1' => password_hash('password1', PASSWORD_BCRYPT),
'user2' => password_hash('password2', PASSWORD_BCRYPT)
];
// 初始化JWT类
$jwt = new JWT('your-secret-key-here');
// 处理登录请求
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['username'], $_POST['password'])) {
$username = $_POST['username'];
$password = $_POST['password'];
if (isset($users[$username]) && password_verify($password, $users[$username])) {
// 认证成功,生成JWT
$token = $jwt->encode([
'sub' => $username,
'role' => 'user'
]);
// 可以设置为HTTP-only cookie或返回给客户端
setcookie('jwt', $token, time() + 3600, '/', '', false, true);
echo json_encode(['token' => $token]);
exit;
} else {
http_response_code(401);
echo json_encode(['error' => 'Invalid credentials']);
exit;
}
}
第三步:保护受限制的路由
创建一个中间件函数来验证JWT并保护路由:
function authenticate() {
$jwt = new JWT('your-secret-key-here');
// 从Cookie或Authorization头获取令牌
$token = $_COOKIE['jwt'] ?? null;
if (!$token && isset($_SERVER['HTTP_AUTHORIZATION'])) {
if (preg_match('/Bearer\s(\S+)/', $_SERVER['HTTP_AUTHORIZATION'], $matches)) {
$token = $matches[1];
}
}
if (!$token) {
http_response_code(401);
echo json_encode(['error' => 'Token not provided']);
exit;
}
$payload = $jwt->decode($token);
if (!$payload) {
http_response_code(401);
echo json_encode(['error' => 'Invalid or expired token']);
exit;
}
return $payload;
}
// 受保护的路由示例
$payload = authenticate();
echo"Welcome, " . htmlspecialchars($payload['sub']) . "!";
第四步:刷新令牌
实现令牌刷新机制以延长会话:
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_COOKIE['jwt'])) {
$payload = $jwt->decode($_COOKIE['jwt']);
if ($payload && isset($payload['sub'])) {
// 验证令牌即将过期(例如在最后5分钟内)
if ($payload['exp'] - time() < 300) {
$newToken = $jwt->encode([
'sub' => $payload['sub'],
'role' => $payload['role']
]);
setcookie('jwt', $newToken, time() + 3600, '/', '', false, true);
echo json_encode(['token' => $newToken]);
exit;
}
}
http_response_code(400);
echo json_encode(['error' => 'Token cannot be refreshed']);
exit;
}
安全注意事项
密钥安全:确保您的JWT密钥足够复杂并安全存储 HTTPS:始终通过HTTPS传输JWT以防止中间人攻击 令牌过期:设置合理的过期时间以减少被盗用的风险 敏感数据:不要在JWT中存储敏感信息,因为它可以被解码(但不是加密的) 注销处理:实现令牌黑名单或使用短期令牌来处理注销