我在 Flutter 应用程序中遇到了 Dio 包的问题。我已经实现了一个拦截器,为每个请求添加标头,并通过简单地注销来处理 401 响应。但是,实施似乎表现出意外,我正在寻求指导来解决该问题。
这是我的 DioClient 类的简化版本:
class DioClient {
Dio dio = Dio(BaseOptions(
connectTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(seconds: 100),
receiveDataWhenStatusError: true,
followRedirects: true,
headers: {"Content-Type": 'application/json'},
));
TokenManager tokenManager = TokenManager();
AppPreference appPreference = AppPreference();
DioClient() {
dio.interceptors
..clear()
..add(InterceptorsWrapper(
onRequest: (options, handler) async {
final accessToken = await tokenManager.getAccessToken();
final refreshToken = await tokenManager.getRefreshToken();
options.headers['Authorization'] = 'Bearer $accessToken';
options.headers['Cookie'] = '$refreshToken';
handler.next(options);
},
onError: (DioException error, handler) async {
if (error.response?.statusCode == 401) {
dio.interceptors.clear();
final options = error.response!.requestOptions;
await tokenManager.deleteTokens();
Hive.box<User>('userBox').clear();
await appPreference.logOut();
showSessionExpiredDialog(navigatorKey.currentContext!);
return handler.reject(DioException(requestOptions: options));
}
return handler.next(error);
},
onResponse: (response, handler) async {
if (response.statusCode == 200 && response.data != null) {
Map<String, dynamic> responseData = response.data;
if (responseData.containsKey("newAccessToken")) {
final newAccessToken = responseData["newAccessToken"];
if (newAccessToken != null) {
await tokenManager.writeAccessToken(newAccessToken);
response.requestOptions.headers['Authorization'] =
'Bearer $newAccessToken';
final refreshToken = await tokenManager.getRefreshToken();
response.requestOptions.headers['Cookie'] = '$refreshToken';
return handler.resolve(response);
}
}
}
return handler.next(response);
},
));
}
}
void showSessionExpiredDialog(BuildContext context) {
showDialog(
barrierDismissible: false,
context: context,
builder: (BuildContext context) {
return AlertDialog(
alignment: Alignment.center,
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text("Session Expired",
style: getSemiBoldStyle(color: Colors.black, fontSize: 16.sp)),
SizedBox(
height: 10.h,
),
SvgPicture.asset(
'assets/svg/security-error.svg',
height: 200.h,
),
SizedBox(
height: 10.h,
),
Text(
"Your session has expired! Please login again",
textAlign: TextAlign.center,
style: getSemiBoldStyle(
color: AppColors.blackPrimaryMinus1,
fontSize: FontSize.s14.sp),
),
],
),
actions: [
TextButton(
child: const Text("OK"),
onPressed: () {
Navigator.of(context).pop();
navigatorKey.currentState
?.pushReplacementNamed(Routes.loginRoute);
},
),
],
);
},
);
}
问题 当用新的访问令牌替换过期的访问令牌时,就会出现问题。在状态代码 401 上,我实现了代码来清除凭据、显示会话过期对话框以及删除令牌。但是,它应该仅在刷新令牌过期时出现。看来,在用新的 accesstoken 替换后,只有少数拦截器会使用它,这意味着旧的拦截器使用过期的令牌,从而导致未经授权的情况。
预期行为 预期行为如下:
为每个请求添加标头(工作正常)。 检查每个响应中是否有新的访问令牌。 如果有新的访问令牌,请替换旧的。 如果错误状态代码为 401,请清除所有凭据,并显示会话过期对话框,提示重新登录
问题 如何确保替换新访问令牌后每个拦截器都使用新令牌而不是旧令牌? 如何确保在处理 401 错误之后、拒绝之前清除拦截器?
我在 Flutter 应用程序中遇到了 Dio 包的问题。我已经实现了一个拦截器,为每个请求添加标头,并通过简单地注销来处理 401 响应。但是,实施似乎表现出意外,我正在寻求指导来解决该问题。
这是我的 DioClient 类的简化版本:
class DioClient {
Dio dio = Dio(BaseOptions(
connectTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(seconds: 100),
receiveDataWhenStatusError: true,
followRedirects: true,
headers: {"Content-Type": 'application/json'},
));
TokenManager tokenManager = TokenManager();
AppPreference appPreference = AppPreference();
DioClient() {
dio.interceptors
..clear()
..add(InterceptorsWrapper(
onRequest: (options, handler) async {
final accessToken = await tokenManager.getAccessToken();
final refreshToken = await tokenManager.getRefreshToken();
options.headers['Authorization'] = 'Bearer $accessToken';
options.headers['Cookie'] = '$refreshToken';
handler.next(options);
},
onError: (DioException error, handler) async {
if (error.response?.statusCode == 401) {
dio.interceptors.clear();
final options = error.response!.requestOptions;
await tokenManager.deleteTokens();
Hive.box<User>('userBox').clear();
await appPreference.logOut();
showSessionExpiredDialog(navigatorKey.currentContext!);
return handler.reject(DioException(requestOptions: options));
}
return handler.next(error);
},
onResponse: (response, handler) async {
if (response.statusCode == 200 && response.data != null) {
Map<String, dynamic> responseData = response.data;
if (responseData.containsKey("newAccessToken")) {
final newAccessToken = responseData["newAccessToken"];
if (newAccessToken != null) {
await tokenManager.writeAccessToken(newAccessToken);
response.requestOptions.headers['Authorization'] =
'Bearer $newAccessToken';
final refreshToken = await tokenManager.getRefreshToken();
response.requestOptions.headers['Cookie'] = '$refreshToken';
return handler.resolve(response);
}
}
}
return handler.next(response);
},
));
}
}
void showSessionExpiredDialog(BuildContext context) {
showDialog(
barrierDismissible: false,
context: context,
builder: (BuildContext context) {
return AlertDialog(
alignment: Alignment.center,
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text("Session Expired",
style: getSemiBoldStyle(color: Colors.black, fontSize: 16.sp)),
SizedBox(
height: 10.h,
),
SvgPicture.asset(
'assets/svg/security-error.svg',
height: 200.h,
),
SizedBox(
height: 10.h,
),
Text(
"Your session has expired! Please login again",
textAlign: TextAlign.center,
style: getSemiBoldStyle(
color: AppColors.blackPrimaryMinus1,
fontSize: FontSize.s14.sp),
),
],
),
actions: [
TextButton(
child: const Text("OK"),
onPressed: () {
Navigator.of(context).pop();
navigatorKey.currentState
?.pushReplacementNamed(Routes.loginRoute);
},
),
],
);
},
);
}
问题 当用新的访问令牌替换过期的访问令牌时,就会出现问题。在状态代码 401 上,我实现了代码来清除凭据、显示会话过期对话框以及删除令牌。但是,它应该仅在刷新令牌过期时出现。看来,在用新的 accesstoken 替换后,只有少数拦截器会使用它,这意味着旧的拦截器使用过期的令牌,从而导致未经授权的情况。
预期行为 预期行为如下:
为每个请求添加标头(工作正常)。 检查每个响应中是否有新的访问令牌。 如果有新的访问令牌,请替换旧的。 如果错误状态代码为 401,请清除所有凭据,并显示会话过期对话框,提示重新登录
问题 如何确保替换新访问令牌后每个拦截器都使用新令牌而不是旧令牌? 如何确保在处理 401 错误之后、拒绝之前清除拦截器?
您可以尝试以下方法。我没有调试您的代码或任何内容,但下面是拦截器类的示例。如果你像这样实现你的拦截器,它应该可以工作。
class Logging extends Interceptor {
final Dio dio;
Logging(this.dio);
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
log('REQUEST[${options.method}] => PATH: ${options.path} \n Extras: ${options.extra}');
super.onRequest(options, handler);
}
@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
log('RESPONSE[${response.statusCode}] => res:: ${json.encode(response.data)}');
super.onResponse(response, handler);
}
@override
void onError(DioError err, ErrorInterceptorHandler handler) {
log('ERROR[${err.response!.statusCode ?? 'Unknown Status Code'}] => PATH: ${err.requestOptions.path}');
super.onError(err, handler);
}
}
然后只需将拦截器添加到你的 dio 客户端即可。
dio.interceptors.addAll({Logging(dio)});