PHP基于JWT实现API身份验证流程
JWT由Header、Payload和Signature三部分组成,用于API无状态身份验证,通过签名防篡改。采用PHP7.4+、Composer及firebase/php-jwt库,该库提供签名与验证功能。用户注册时用password_hash()加密密码,登录后生成带过期时间的JWT令牌,服务端验证令牌确保数据传输安全与防伪造,并支持刷新。
1. JWT简介
在了解 JWT(JSON Web Token)之前,先看看一个典型的 JWT 令牌长什么样。它由三个部分组成,用点号连接,就像这样:xxxxx.yyyyy.zzzzz。这三个部分各自承担着不同的角色,构成了 JWT 身份验证的基础。

Header – 这部分声明令牌类型和签名算法,比如 HMAC SHA256 或 RSA,是 JWT 令牌的头部信息。
Payload – 这部分存放实际的数据声明(claims),包括注册声明、公共声明和私有声明三种。这里携带了用户身份等关键信息,是 JWT 的核心内容。
Signature – 这部分对前两部分进行签名,保证令牌在传输过程中没有被篡改过,确保 JWT 的安全性。
理解这三个部分,就等于拿到了 JWT 的入场券。接下来,我们动手搭建一个完整的 PHP 环境下 JWT 验证流程。
2. 环境准备
工欲善其事,必先利其器。建议 PHP 版本在 7.4 以上,并且装好 Composer。为了简化 JWT 的生成和验证,我们选用 firebase/php-jwt 这个成熟库,它能高效处理 JWT 令牌的核心操作。
composer require firebase/php-jwt
3. 用户注册与登录
3.1 数据库设计
既然是身份验证,用户表少不了。这里用 MySQL,建一个最简单的 users 表,用于存储 JWT 身份验证所需的用户信息:
CREATE TABLE `users` ( `id` INT AUTO_INCREMENT PRIMARY KEY, `username` VARCHAR(50) NOT NULL UNIQUE, `password` VARCHAR(255) NOT NULL );
注意: 密码绝对不能明文存储。使用 PHP 内置的 password_hash() 函数进行哈希处理,这是 JWT 验证流程中最基本的安全底线。
3.2 注册接口
注册逻辑很直接:接收用户名和密码,哈希密码后写入数据库。不过要先检查用户名是否已被占用,避免重复注册。注册成功后即可用于后续的 JWT 登录生成令牌。
prepare("SELECT id FROM users WHERE username = ?");
$stmt->execute([$username]);
if ($stmt->fetch()) {
return ['error' => 'User already exists'];
}
// 哈希密码
$hash = password_hash($password, PASSWORD_DEFAULT);
$stmt = $pdo->prepare("INSERT INTO users (username, password) VALUES (?, ?)");
$stmt->execute([$username, $hash]);
return ['message' => 'User registered successfully'];
}
3.3 登录接口
登录时验证密码,验证通过后生成一个 JWT 令牌返回。密钥 secret_key 必须足够复杂,并且绝对不能硬编码在代码里——用环境变量来管理,这是保证 JWT 签名安全的关键。
function login($username, $password) {
global $pdo;
$stmt = $pdo->prepare("SELECT id, password FROM users WHERE username = ?");
$stmt->execute([$username]);
$user = $stmt->fetch();
if (!$user || !password_verify($password, $user['password'])) {
return ['error' => 'Invalid credentials'];
}
// 生成JWT
$secret_key = "your_secret_key"; // 应该是一个复杂的随机字符串,存储在环境变量中
$payload = [
'iss' => 'localhost', // 签发者
'aud' => 'localhost', // 接收方
'iat' => time(), // 签发时间
'exp' => time() + 3600, // 过期时间(1小时)
'data' => [
'user_id' => $user['id'],
'username' => $username
]
];
$jwt = JWT::encode($payload, $secret_key, 'HS256');
return ['token' => $jwt];
}
4. 验证JWT
当客户端请求需要登录的接口时,比如获取个人信息,它需要在 HTTP 头部带上 Authorization 字段,值形如 Bearer xxxxx.yyyyy.zzzzz。服务端拿到后解码并验证签名,这是 JWT 验证流程的核心步骤。
function getProfile() {
// 获取Authorization头
$headers = getallheaders();
if (!isset($headers['Authorization'])) {
http_response_code(401);
return ['error' => 'Token not provided'];
}
$token = str_replace('Bearer ', '', $headers['Authorization']);
$secret_key = "your_secret_key";
try {
$decoded = JWT::decode($token, new FirebaseJWTKey($secret_key, 'HS256'));
// 验证成功后,用$decoded->data->user_id去数据库取用户信息
$user_id = $decoded->data->user_id;
// ... 数据库查询代码 ...
return ['user' => $userInfo];
} catch (Exception $e) {
http_response_code(401);
return ['error' => 'Invalid token'];
}
}
注意 decode 使用的是新版的 Key 对象写法——Firebase JWT 库更新后的标准用法就是这样,确保 JWT 验证的正确性。
5. 安全性考虑
- 使用 HTTPS JWT 本质上还是个 Token 明文在网络里跑,没有 HTTPS 就意味着裸奔,务必加上 TLS/SSL 加密通道,防止 JWT 令牌泄露。
- 密钥安全 密钥(
secret_key)必须足够复杂(比如 128 位随机字符串),并且用环境变量或者配置中心管理,绝不要硬编码提交到版本库,这是 JWT 签名安全的基本要求。 - 令牌过期 设置合理的有效期(比如 1 小时),并搭配刷新令牌机制,避免用户频繁重新登录,同时也能降低 JWT 令牌被盗用后的风险窗口。
- 存储方式 客户端通常把 JWT 放在 localStorage 或 sessionStorage 里,但这容易被 XSS 攻击窃取。更好的选择是用 HttpOnly 的 Cookie 存储,能有效防止 XSS,不过这时候要注意防范 CSRF 攻击,需要额外加 anti-CSRF token 等措施,确保 JWT 身份验证的整体安全。
6. 总结
至此,我们完整走了一遍在 PHP 中使用 JWT 做 API 身份验证的流程:从用户注册登录到 JWT 令牌生成,再到受保护接口的 JWT 验证。JWT 最大的好处是无状态、跨域友好,特别适合前后端分离和微服务架构。当然,JWT 安全不是一套就能结束的——输入验证、防 SQL 注入、定期轮换密钥等配套措施一个都不能少。希望这份流程能给你一个扎实的起点,剩下的细节可以在实际项目中慢慢打磨。
