Dio中自动刷新JWT令牌:拦截器与查询拦截器详解
服务器通过Authorization头验证授权,格式为<认证方案> <认证参数>。中间件为所有受保护端点处理此头信息。如果数据缺失或无效,则返回401未授权或403禁止访问。
主要方案:
- Basic:Base64编码的登录名:密码(
Authorization: Basic bG9naW46cGFzc3dvcmQ=)。安全性低,易于解码。 - Bearer:JWT令牌(
Authorization: Bearer <令牌>)。支持过期,需要刷新。
JWT由头部(算法)、载荷(用户数据、exp)、签名(服务器密钥)组成。格式为Base64编码的{头部}.{载荷}.{签名}。
JWT结构与刷新机制
载荷包含exp(过期时间)。当收到401时,客户端通过一个不受中间件保护的端点请求刷新令牌。
双令牌系统:
- 访问令牌:短期有效(几分钟),用于API请求。
- 刷新令牌:长期有效,用于生成新的访问令牌。
处理过期令牌的策略
登出(最简单方法)
收到401时——登出。实现简单,但对用户不便。
优点:
- 代码量最小。
缺点:
- 频繁重新登录。
- 不兼容双令牌系统。
错误时刷新(拦截器)
通过Dio的Interceptor实现。在onRequest中添加令牌,在onError中处理401错误。
class TokenInterceptor extends Interceptor {
final TokenStorage _storage;
TokenInterceptor(this._storage);
@override
Future<void> onRequest(RequestOptions options, RequestInterceptorHandler handler) async {
final token = await _storage.getToken();
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
handler.next(options);
}
}
设置:
final dio = Dio()..interceptors.add(TokenInterceptor(TokenStorage()));
在onError中处理401:
- 检查
_isRefreshing标志。 - 如果未刷新——通过单独的Dio实例启动刷新。
- 排队请求通过
Completer<void> _refreshCompleter等待。
Future<String?> _updateToken() async {
final oldToken = await _storage.getToken();
try {
final dio = Dio();
final response = await dio.get('/auth/refresh/', headers: {'Authorization': 'Bearer $oldToken'});
final newToken = (response.data as Map<String, dynamic>)['token'];
await _storage.setNewToken(newToken);
return newToken;
} catch (e) {
await _storage.clearToken();
return null;
}
}
完整的onError逻辑同步并行请求,使用新令牌重试。
优点:
- 适用于双令牌系统。
- 无需外部库。
缺点:
- 大量重试可能导致服务器负载波动。
- 响应延迟(错误 + 刷新 + 重试)。
主动刷新(查询拦截器)
使用JWT中的exp在过期前刷新。QueryInterceptor确保顺序处理请求。
解析JWT提取exp:
- 解码Base64载荷。
- 解析JSON,获取时间戳。
优点:避免阻塞性401错误。缺点:严格队列,并行请求被阻塞。
关键要点
- 使用
Interceptor进行响应式刷新,配合Completer同步。 QueryInterceptor适合基于exp的主动刷新。- 将令牌存储在隔离存储中,使用单独的Dio实例进行刷新。
- 处理多个401时的竞态条件。
- 双令牌增强安全性:短期访问 + 刷新令牌。
— Editorial Team
暂无评论。