Dio 拦截器问题:处理 401 响应和刷新访问令牌

Dio Interceptor Issue: Handling 401 Responses and Refreshing Access Token

提问人:Bishal Rumba 提问时间:11/17/2023 更新时间:11/17/2023 访问量:35

问:

我在 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);
            },
          ),
        ],
      );
    },
  );
}

问题将过期的 accesstoken 替换为新的 accesstoken 时会出现此问题。在状态代码 401 上,我实现了清除凭据、显示会话过期对话框和删除令牌的代码。但是,它应仅在刷新令牌过期时显示。似乎在用新的accesstoken替换accesstoken后,只有少数拦截器会使用它,这意味着旧的拦截器使用过期的令牌,从而导致未经授权的情况。

预期行为预期行为如下:

将标头添加到每个请求(工作正常)。 检查每个响应中是否有新的访问令牌。 如果有新的访问令牌,请替换旧的访问令牌。 如果错误状态代码为 401,请清除所有凭据,并显示会话过期对话框,提示再次登录

问题如何确保替换新访问令牌后的每个拦截器都使用该新令牌而不是旧令牌? 如何确保在处理 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);
            },
          ),
        ],
      );
    },
  );
}

问题将过期的 accesstoken 替换为新的 accesstoken 时会出现此问题。在状态代码 401 上,我实现了清除凭据、显示会话过期对话框和删除令牌的代码。但是,它应仅在刷新令牌过期时显示。似乎在用新的accesstoken替换accesstoken后,只有少数拦截器会使用它,这意味着旧的拦截器使用过期的令牌,从而导致未经授权的情况。

预期行为预期行为如下:

将标头添加到每个请求(工作正常)。 检查每个响应中是否有新的访问令牌。 如果有新的访问令牌,请替换旧的访问令牌。 如果错误状态代码为 401,请清除所有凭据,并显示会话过期对话框,提示再次登录

问题如何确保替换新访问令牌后的每个拦截器都使用该新令牌而不是旧令牌? 如何确保在处理 401 错误后和拒绝 401 错误之前清除拦截器?

Flutter 身份验证 HTTP 拦截器 DIO

评论


答: 暂无答案