发布于2026-07-09 阅读(0)
扫一扫,手机访问
Token续期这件事,很多开发者第一反应是检查exp过期时间——这逻辑本身没错,但在APP场景下,隐患其实不小。你想想,用户正刷着列表、发着评论,结果Token悄无声息地过期了,应用直接把人踢回登录页,这体验可就太糟糕了。问题的根源在于,传统的过期判断只关注“是否超时”,而忽略了“用户正在活跃”这个关键前提。
滑动过期机制(Sliding Expiration)的解决方案,其实相当优雅。核心思路是:只要用户在有效期内发起一次合法请求,就自动延长Token的生命周期。注意,不是无限延长,而是将过期时间重置为“当前时间+固定有效期”。
这里有个关键设计必须拎清楚:续期操作必须发生在认证通过之后、业务逻辑执行之前。并且,除非万不得已,新Token不应该直接返回给前端。为什么?一旦放开这个口子,并发问题、响应头污染这些麻烦事儿就会接踵而至。
具体怎么实现呢?推荐的做法是在全局认证中间件(比如app/middleware/AuthToken.php)里统一处理,别把这段逻辑散落到每个控制器里去重复劳动。
常规流程大致是这样:先调用$this->auth->check()验证原始Token是否合法。这里有个细节——对于“即将过期”的Token(比如剩余有效期不足300秒),也要视为有效。验证通过后,读取Payload中的iat和exp。如果当前时间距离exp已经小于预设的滑动窗口(比如600秒),那就生成一个新Token。新Token的核心变化只有iat和exp,其他字段一律保留。
至于新Token怎么处理?除非接口文档明确约定要通过响应头(如X-Auth-Token)发送给前端,否则服务端内部在Redis里刷新一下状态就够了。
写一段基于firebase/php-jwt的示意代码,可以帮助理解这个过程:
$payload = $this->auth->getPayload();
$now = time();
$exp = $payload['exp'];
if ($exp - $now < 600) {
$newPayload = array_merge($payload, [
'iat' => $now,
'exp' => $now + 7200 // 2小时有效期
]);
$newToken = JWT::encode($newPayload, config('jwt.secret'), 'HS256');
// 写入 Redis:key=token:{$oldTokenHash}, value=$newToken, expire=7200+300(留缓冲)
}
JWT自包含的特性确实方便,但它不支持主动吊销和滑动刷新。所以,服务端状态存储是绕不开的一环,而Redis是大多数团队的首选。不过,实际操作中有几个容易翻车的点:
sha256($token)或base64(urlencode($token))作为键。EXPIRE的方式不够安全。应该在生成新Token时,用SETEX设置带过期的值,并且过期时间要比JWT本身的exp长一些(比如多300秒),防止系统时间偏差导致Token提前作废。GET再SETEX,很容易写入不同版本。用Lua脚本把这两个操作封装成一个原子步骤,是从源头解决问题的方式。如果用的是ThinkPHP的cache门面,默认的Redis驱动可能不支持Lua,这时候直连Redis实例会更稳妥。网上有不少教程教你通过响应头X-Auth-Token来更新Token,APP拦截后直接覆盖本地存储。这个方法看起来方便,实际坑多得很:
localStorage里相互覆盖,结果谁都不知道该用哪个。更稳的做法是:APP只在登录成功时存一次Token。后续所有请求都拿这个Token去发。服务端在认证中间件里静默续期,把新Token写入Redis。就算某次请求因为网络问题没收到响应,下一次还能用旧Token发起请求——只要它还在滑动窗口内,服务端照常接受并续期。
真正需要主动通知APP更新Token的场景其实很少。比如管理员后台强制踢人,那应该走独立的长连接或消息通道,而不是混在业务API里。
说到底,滑动续期真正的技术难点不在代码多长,而在于时间窗口、存储一致性、客户端行为这三者的对齐。少考虑一个环节,就会出现“我以为续上了,其实根本没生效”的尴尬局面。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8