如何请求在多个 api 调用中调用一次的刷新令牌

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

我有一个函数,如果之前的 API 响应返回错误代码 1000,则刷新令牌。但是,当同时进行多个 API 调用时,会导致多个刷新令牌请求。我想确保刷新令牌只被调用一次。

这是我的代码


  requestGet(String endPoint, Map<String, dynamic> params, [bool isUsingToken = false]) async {
    String sign = getSign(timestamp + jsonEncode(params));
    String deviceId = await SharedPrefsService().getDeviceId();
    String token = await SharedPrefsService().getToken();;

    final response = await httpGet(endPoint, params, sign, token, deviceId, isUsingToken);
    dynamic result = response;

    var isRenewed = await renewTokenIfNeeded(deviceId, result, endPoint);
    if (isRenewed) {
      token = await SharedPrefsService().getToken();
      final renewedResponse = await httpGet(endPoint, params, sign, token, deviceId, isUsingToken);
      result = renewedResponse;
    }
    return result;
  }
  Future<bool> renewTokenIfNeeded(String deviceId, result) async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    bool renewingToken = prefs.getBool('renewingToken') ?? false;
    if (result['error_code'] == '1000') {
      prefs.setBool('renewingToken', true);
      try {
        if (renewingToken) {
          return true;
        }
        var isRenewed = await requestRenewToken(deviceId);
        if (isRenewed) {
          prefs.setBool('renewingToken', false);
          return true;
        }
      } finally {
        prefs.setBool('renewingToken', false);
      }
    }
    return false;
  }
  requestRenewToken(String deviceId) async {
    var refresh = await AuthenticationService().refreshToken();
    if (refresh.errorCode == '9999') {
      SharedPrefsService().clearAllData();
      return false; // then  back to sign in
    }
    if (refresh.errorCode == '0000') {
      SharedPrefsService().saveTokenData(refresh.token!, refresh.userName!, deviceId);
      return true;
    }
    return false;
  }

我尝试过使用同步和互斥包,但它们似乎不起作用,我更喜欢尽量减少外部包的使用。您能建议一个解决方案吗?谢谢!

flutter dart asynchronous jwt mutex
1个回答
0
投票

您可以参考下面的示例,对于多个请求的情况,没有 QueuedInterceptor,并且

  • 仅触发调用刷新令牌流程01次
  • 保留所有请求并在执行刷新令牌成功后重试
class AuthInterceptor extends InterceptorsWrapper {
  final Dio dio;

  AuthInterceptor(this.dio);

  // when accessToken is expired & having multiple requests call
  // this variable to lock others request to make sure only trigger call refresh token 01 times
  // to prevent duplicate refresh call
  bool _isRefreshing = false;

  // when having multiple requests call at the same time, you need to store them in a list
  // then loop this list to retry every request later, after call refresh token success
  final _requestsNeedRetry = <({RequestOptions options, ErrorInterceptorHandler handler})>[];

  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) async {
    final accessToken = getAccessTokenFromLocalStorage();
    options.headers['authorization'] = 'Bearer $accessToken';
    return handler.next(options);
  }

  @override
  void onError(DioException err, ErrorInterceptorHandler handler) async {
    final response = err.response;
    if (response != null &&
        // status code for unauthorized usually 401
        response.statusCode == 401 &&
        // refresh token call maybe fail by it self
        // eg: when refreshToken also is expired -> can't get new accessToken
        // usually server also return 401 unauthorized for this case
        // need to exlude it to prevent loop infinite call
        response.requestOptions.path != "path/your/endpoint/refresh") {
      // if hasn't not refreshing yet, let's start it
      if (!_isRefreshing) {
        _isRefreshing = true;

        // add request (requestOptions and handler) to queue and wait to retry later
        _requestsNeedRetry.add((options: response.requestOptions, handler: handler));

        // call api refresh token
        final isRefreshSuccess = await _refreshToken();

        _isRefreshing = false;

        if (isRefreshSuccess) {
          // refresh success, loop requests need retry
          for (var requestNeedRetry in _requestsNeedRetry) {
            // don't need set new accessToken to header here, because these retry
            // will go through onRequest callback above (where new accessToken will be set to header)
            final retry = await dio.fetch(requestNeedRetry.options);
            requestNeedRetry.handler.resolve(retry);
          }
          _requestsNeedRetry.clear();
        } else {
          _requestsNeedRetry.clear();
          // if refresh fail, force logout user here
        }
      } else {
        // if refresh flow is processing, add this request to queue and wait to retry later
        _requestsNeedRetry.add((options: response.requestOptions, handler: handler));
      }
    } else {
      // ignore other error is not unauthorized
      return handler.next(err);
    }
  }

  Future<bool> _refreshToken() async {
    try {
      final refreshToken = getRefreshTokenFromLocalStorage();
      final res = await callApiRefreshToken(refreshToken);
      if (res.response.statusCode == 200) {
        print("refresh token success");
        final refreshResponse = RefreshResponse.fromJson(res.data);
        // save new access + refresh token to your local storage for using later
        setAccessTokenToLocalStorage(refreshResponse.accessToken);
        setRefreshTokenToLocalStorage(refreshResponse.refreshToken);
        return true;
      } else {
        print("refresh token fail ${res.response.statusMessage ?? res.response.toString()}");
        return false;
      }
    } catch (error) {
      print("refresh token fail $error");
      return false;
    }
  }
}
© www.soinside.com 2019 - 2024. All rights reserved.