提问人:Ampsyy 提问时间:11/7/2023 最后编辑:Ampsyy 更新时间:11/8/2023 访问量:39
Flutter DIO 刷新令牌循环
Flutter DIO Refresh Token Loop
问:
我正在构建一个主要利用 API 调用来运行的 Flutter 应用程序,但是在持有者令牌过期后(每 4 小时一次)遇到 401 未经授权的错误。
在这种情况下,我尝试实现 DIO 拦截器来处理请求,但查看网络调试日志时,请求会重复发送,即使在收到返回的状态代码 200 时也是如此。(我通过使用我的登录页面并在 requestToken 函数中发送不正确的密码并在 refreshToken 函数中硬编码正确的密码来复制 401 和 200)
我已将所有 API 调用放在一个单独的文件 apihelper.dart 中,部分代码如下所示:
class APIHelper {
StorageService storageService = StorageService();
Dio dio = Dio(
BaseOptions(
connectTimeout: Duration(seconds: 120),
receiveTimeout: Duration(seconds: 120),
),
);
final _storage = const FlutterSecureStorage();
APIHelper() {
dio.interceptors.add(
InterceptorsWrapper(
onRequest: (options, handler) async {
final bearerToken = await _storage.read(key: 'token');
final tenantId = await _storage.read(key: 'tenant');
if (options.path != '/api/tokens') {
// Add the access token to the request header
options.headers['Authorization'] = bearerToken;
options.headers['tenant'] = tenantId;
return handler.next(options);
}
options.headers['tenant'] = tenantId;
return handler.next(options);
},
onError: (DioException e, handler) async {
if (e.response?.statusCode == 401) {
// If a 401 response is received, refresh the access token
String newAccessToken = await refreshToken();
// Update the request header with the new access token
e.requestOptions.headers['Authorization'] =
'Bearer $newAccessToken';
// Repeat the request with the updated header
return handler.resolve(await dio.fetch(e.requestOptions));
}
},
),
);
}
我的初始 RequestToken 函数如下:
Future requestToken(String username, String password, String tenant) async {
var credsdata = {"email": username, "password": password};
String baseUrl = await getBaseUrl();
dio.options.baseUrl = baseUrl; // Set the dynamic base URL
print('requestToken() Called');
print('requestToken() Body: $credsdata');
try {
Response response = await dio.post('/api/tokens', data: credsdata);
Map<String, dynamic> data = response.data;
var token = data['token'];
var refreshToken = data['refreshToken'];
await storageService.saveStorageData('token', token);
await storageService.saveStorageData('refreshtoken', refreshToken);
print(refreshToken);
return 'Authentication Success';
} on DioException catch (e) {
return e.response?.statusMessage;
}
}
我的 RefreshToken 函数如下所示:
Future refreshToken() async {
final savedUser = await _storage.read(key: 'username');
final savedPasswd = await _storage.read(key: 'password');
var credsdata = {"email": savedUser, "password": 'TEMP HARDCODED PASSWORD'};
print('refreshToken() Called');
print('refreshToken() Body: $credsdata');
String baseUrl = await getBaseUrl();
dio.options.baseUrl = baseUrl; // Set the dynamic base URL
try {
Response response = await dio.post('/api/tokens', data: credsdata);
Map<String, dynamic> data = response.data;
var token = data['token'];
var refreshToken = data['refreshToken'];
await storageService.saveStorageData('token', token);
await storageService.saveStorageData('refreshtoken', refreshToken);
return token;
} on DioException catch (e) {
return e.response?.statusMessage;
}
}
网络调试截图状态代码为 200 的记录具有来自 refreshToken() 的硬编码密码,而 401 日志具有使用 UI 表单传递的密码。
一旦返回状态代码 200,如何让它停止尝试?
答:
0赞
Shahed Emon
11/8/2023
#1
我在下面提供了我如何处理它。这可能会有所帮助。
@override
void onError(err, handler) async {
if (err.response?.statusCode == 401) {
final prefAccessToken = await _preferenceManager.getAccessToken();
try {
String? errAccessToken = err.requestOptions.headers["accesstoken"];
if (errAccessToken != null &&
errAccessToken.isNotEmpty &&
prefAccessToken == errAccessToken) {
await _preferenceManager.updateTokens(
accessToken: null, accessTokenExp: null);
}
var value = await dio.post(
"${DioProvider.baseUrl}/user/refresh-token",
options: Options(
headers: {
'refreshtoken': await _preferenceManager.getRefreshToken(),
...getAdditionalHeaders(),
},
),
);
if (value.statusCode == 201 || value.statusCode == 200) {
var data = value.data["data"];
var accessToken = data["accessToken"];
var refreshToken = data["refreshToken"];
var accessTokenExp = data["accessTokenExp"];
await _preferenceManager.updateTokens(
accessToken: accessToken,
refreshToken: refreshToken,
accessTokenExp: accessTokenExp,
);
await AuthController.instance().getSettings();
return handler.resolve(
await _cloneDioErrorRequestWithAccesstoken(err, accessToken));
} else {
throw "";
}
} catch (e) {
/**
* Means refresh token is invalid!
* But, there is a possibility of concurrent refresh-token api call
* So, let's check if access token is there!
* */
if (prefAccessToken.isEmpty) {
/** Means accesstoken (or, new accesstoken in parallel case) and refreshtoken both are expired */
await _preferenceManager.updateTokens(
accessToken: null,
refreshToken: null,
accessTokenExp: null,
);
await AuthController.instance().getSettings();
await goToNamedAndClearAll(AppRoutes.start);
} else {
/** Means new accesstoken is not expired */
return handler.resolve(
await _cloneDioErrorRequestWithAccesstoken(err, prefAccessToken));
}
}
} else {
super.onError(err, handler);
}
}
评论