Flutter DIO 刷新令牌循环

问题描述 投票:0回答:1

我正在构建一个 Flutter 应用程序,主要利用 API 调用来运行,但是在不记名令牌过期后(每 4 小时)遇到 401 未经授权的错误。

我尝试实现一个 DIO 拦截器来处理这种情况下的请求,但查看网络调试日志时,请求会重复发送,即使收到返回的状态代码 200 也是如此。 (我通过使用我的登录页面并在 requestToken 函数中发送不正确的密码并在刷新令牌函数中硬编码正确的密码来复制 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;
    }
  }

screenshot of Network Debug 状态代码为 200 的记录具有来自 refreshToken() 的硬编码密码,401 日志具有使用 UI 表单传递的密码。

返回状态码 200 后如何让它停止尝试?

flutter access-token refresh-token dio
1个回答
0
投票

我在下面提供了我的处理方法。这可能会有所帮助。

  @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);
    }
  }
© www.soinside.com 2019 - 2024. All rights reserved.