jwt签发需用firebase/php-jwt库构造header(alg:hs256, typ:jwt)和payload(含iat、exp、jti及用户身份信息),密钥须从环境变量读取;校验须完整验证签名、时效及黑名单;刷新推荐双token机制,at短效、rt长效且绑定设备并存redis,每次刷新即失效旧rt。

JWT签发:生成带签名与过期时间的Token
使用PHP生成JWT需手动构造Header、Payload并用密钥签名。推荐使用firebase/php-jwt库(稳定、轻量、无依赖),避免自行拼接Base64URL导致的安全或兼容问题。
签发时关键点:
- Header固定为
{"alg":"HS256","typ":"JWT"},算法必须与验签一致 - Payload至少包含
iat(签发时间)、exp(过期时间),建议添加jti(唯一ID)防重放 - 密钥(
$secret)应存于环境变量或配置中心,禁止硬编码在代码中 - 用户身份信息(如
uid、role)放入Payload,但敏感字段(密码、手机号)绝不能写入
示例代码:
use Firebase\JWT\JWT; use Firebase\JWT\Key; <p>$payload = [ 'uid' => 123, 'role' => 'user', 'iat' => time(), 'exp' => time() + 3600, // 1小时后过期 'jti' => bin2hex(random_bytes(16)) ];</p><p>$token = JWT::encode($payload, $_ENV['JWT_SECRET'], 'HS256');</p>
JWT校验:安全解析并验证签名与时效
校验不是简单解码,而是完整验证签名有效性、时间戳合法性及可选的黑名单/白名单逻辑。
核心步骤:
- 捕获
ExpiredException、SignatureInvalidException等异常,统一返回401错误 - 使用
JWT::decode()传入new Key($secret, 'HS256')(v6.0+语法),禁用JWT::setSecret()等旧接口 - 检查
exp和nbf(生效时间)字段,确保当前时间在有效窗口内 - 业务层可扩展校验:
jti是否已在Redis黑名单中、uid对应用户是否被禁用
示例代码:
try {
$decoded = JWT::decode($token, new Key($_ENV['JWT_SECRET'], 'HS256'));
// $decoded 是 stdClass 对象,可转数组:(array) $decoded
} catch (\Firebase\JWT\ExpiredException $e) {
http_response_code(401);
echo json_encode(['error' => 'Token expired']);
} catch (\Firebase\JWT\SignatureInvalidException $e) {
http_response_code(401);
echo json_encode(['error' => 'Invalid signature']);
} catch (\Exception $e) {
http_response_code(401);
echo json_encode(['error' => 'Invalid token']);
}
Token刷新:双Token方案实现无缝续期
单Token无法兼顾安全性与体验——设短过期易频繁登录,设长过期则风险高。推荐“Access Token + Refresh Token”双机制:
- Access Token(AT):短时效(如15–30分钟),用于常规API请求,不存数据库
- Refresh Token(RT):长时效(如7天),强绑定设备/IP/UserAgent,存储在Redis中并设置过期时间
- RT仅用于换取新AT,每次使用后立即失效并生成新RT(即“一次一换”)
- RT泄露可主动吊销:删除Redis中对应key,或增加
rt_version字段比对
刷新接口逻辑示例:
// 请求头携带 RT(如 Authorization: Bearer <refresh_token>)
$rt = getBearerToken(); // 自定义函数提取
<p>// 1. 校验RT签名与基础时效
$rtData = JWT::decode($rt, new Key($_ENV['RT_SECRET'], 'HS256'));</p>
<p>// 2. 查询Redis确认RT未被撤销且匹配用户
$redisKey = "rt:{$rtData->uid}:{$rtData->jti}";
if (!$redis->get($redisKey)) {
throw new Exception('Refresh token invalid or revoked');
}</p>
<p>// 3. 生成新AT和RT
$newAT = JWT::encode([...], $_ENV['JWT_SECRET'], 'HS256');
$newRT = JWT::encode([
'uid' => $rtData->uid,
'jti' => bin2hex(random_bytes(16)),
'iat' => time(),
'exp' => time() + 86400 * 7
], $_ENV['RT_SECRET'], 'HS256');</p>
<p>// 4. 写入新RT到Redis,旧RT自动失效
$redis->setex($redisKey, 86400 * 7, 'valid');</p>
<p>// 返回新Token对
echo json_encode(['access_token' => $newAT, 'refresh_token' => $newRT]);</p></refresh_token>安全加固与常见避坑点
JWT不是银弹,落地时需主动防御典型风险:
-
HTTPS强制:所有含Token的请求必须走HTTPS,HTTP下Cookie的
Secure标志才生效 -
Token传输方式:优先用
Authorization: Bearer <token></token>,避免存入localStorage(XSS可读);若用Cookie,设HttpOnly+Secure+SameSite=Strict - 敏感操作二次验证:修改密码、支付等动作,即使AT有效,也应要求短信/邮箱验证码
-
密钥轮换支持:生产环境定期更换
JWT_SECRET,解码时尝试多个密钥(旧密钥用于验老Token,新密钥签发新Token) -
不要信任Payload中的任意字段:如
role需在服务端查DB确认,防止客户端篡改伪造权限
不复杂但容易忽略。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











