jwt生成时必须设置合理exp和iat:exp防止永不过期风险,iat支持防重放校验;均需用time()动态计算,避免硬编码时间戳,且exp须为int型、大于iat。

JWT token生成时必须设置合理的exp和iat
PHP生成JWT不能只拼签名,exp(过期时间)和iat(签发时间)是校验逻辑的基石。漏设exp会导致token永不过期,设成固定时间戳(如time() + 3600)又容易在分布式时钟不同步场景下被拒绝。
- 用
time()动态计算,不要硬编码时间戳 -
exp建议比iat大整数秒(如3600),避免毫秒级或微秒级偏差干扰验证 - 若需支持“滑动过期”,
exp不能用于刷新判断——得靠独立的refresh_exp字段或数据库记录 - 使用
firebase/php-jwt时,setExpiration()和setIssuedAt()必须显式调用,不传参会默认为null,导致payload里没这两个键
刷新token不能只重签一个新token
单纯用旧token解码后改exp再签名,等于把过期控制权完全交给客户端——攻击者截获旧token就能无限续期。真正安全的刷新需要服务端参与状态管理。
- 每次刷新应生成全新
jti(JWT ID),并存入Redis,设置与refresh_exp一致的TTL - 验证refresh请求时,先查Redis是否存在该
jti,存在才签发新access token,并立即DEL原jti - access token本身不存
refresh_token字段,refresh token必须单独发放、HttpOnly Cookie传输、且路径限定(如/api/auth/refresh) - 不要把refresh token塞进JWT payload——它不是用来被客户端解析的,而是服务端密钥校验+存储比对的凭证
firebase/php-jwt验证失败常见报错及对应修复
实际部署中最常卡在SignatureInvalidException或BeforeValidException,往往不是算法问题,而是环境或时钟细节没对齐。
-
SignatureInvalidException:检查JWT::decode()传入的密钥是否与签发时完全一致(注意前后空格、换行、base64解码与否) -
BeforeValidException:服务器时间比NTP快几秒就会触发,用date -s "$(curl -s --head http://google.com | grep '^Date:' | sed 's/Date: //g')"粗略校准,或在JWT::decode()第3个参数加['leeway' => 5]容忍5秒偏差 -
ExpiredException:确认exp字段值是int类型(不是字符串),且未被JSON decode自动转成float(PHP 7.4+默认不会,但某些自定义decoder会) - 用
JWT::jsonDecode()手动解析header/payload调试时,注意它返回stdClass,不是array,直接isset($payload->exp)可行,但array_key_exists()会失效
access token过期后,refresh流程必须区分错误类型返回不同HTTP状态码
前端需要靠状态码决定是跳登录页还是静默刷新,而不仅仅是看message字段。服务端不规范返回会让前端鉴权逻辑混乱。
- refresh token已失效或不存在 →
401 Unauthorized,响应体含{"error": "refresh_token_invalid"} - refresh token格式错误或签名失败 →
400 Bad Request,避免暴露服务端校验细节 - access token过期但refresh有效 →
200 OK,返回新access token和新的refresh token(可选,取决于是否启用滚动刷新) - 同一refresh token重复使用 →
403 Forbidden,并记录告警,这是典型重放攻击信号
GETSET或Lua脚本比先GET再DEL更可靠;JWT本身只是载体,真正的权限边界在你如何管理refresh生命周期。php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











