使用ASP.NET Core WebAPI的Dart中的令牌刷新并发问题

时间:2018-10-08 05:44:02

标签: asp.net asp.net-core dart flutter jwt

我使用Dart在Flutter中编写了一个简单的应用程序。我使用JWT令牌来认证用户。主令牌仅在60秒内有效。 当用户发送带有过期令牌的请求时,webapi返回401。 然后在我的Dart代码中,检查响应的statuscode是否为401 如果是,那么我向RefreshToken端点发送一个请求,然后再发送一次请求(此请求较早返回401)。

如果用户执行许多操作的速度过快,则过期的令牌会多次更新。 我想避免这种情况。 当令牌正在刷新时,完美的实现是,其他请求应等待。

2 个答案:

答案 0 :(得分:1)

我遇到了类似的问题,并尝试使用以下方法解决。

我使用flutter-redux来管理客户端的状态。

  1. 登录后获取jwt令牌
  2. 根据服务器的响应,解码客户端的jwt令牌。
  3. 它包含一个超时时间-到期时间。
  4. 在客户端创建Redux中间件,说_createRefreshTokenMiddleware
  5. 来自客户端的每个请求都应先经过此中间件,然后再发送到服务器。
  6. 在此中间件中,对于服务器的每个请求,都要检查令牌超时,如果令牌已过期,则保留那些请求,将请求发送到服务器以刷新令牌,等到收到新令牌后,再使用此新令牌来发送令牌请求服务器。
  7. 令牌将过期的所有其他请求将等待一个共同的承诺,比如说refreshTokenPromise来首先获取refreshToken。这样,您不必发送多个refreshToken请求。
  8. 如果令牌仍然有效,则让请求通过。

请参阅下面的示例-

您的中间件:

Middleware<AppState> _createRefreshTokenMiddleware() {
  return (Store store, action, NextDispatcher next) async {
    AppState appState = store.state;
    AuthState auth =  appState.auth;

    if (isTokenExpired(auth)) {
      if (auth.refreshTokenPromise == null) {
        refreshToken(store).then((res) => next(action));
      } else {
        auth.refreshTokenPromise.then((res) => next(action));
      }
    }
    next(action);
  };
}

令牌已过期的所有请求都将在refreshTokenPromise上等待解析,一旦解决,所有待处理的请求将在请求标头中设置新的更新令牌(例如)。

检查令牌到期:

bool isTokenExpired(AuthState auth) {
  int bufferSeconds = 10;
  if(auth != null && auth.authTokens != null && auth.authTokens.tokenExpiryTime != null) {
    var currentTime = DateTime.now();
    Duration durationRemaining = auth.authTokens.tokenExpiryTime.difference(currentTime);

    return (durationRemaining.inSeconds - bufferSeconds) <= 0 ? true : false;

  }
  return false;
}

您在令牌实际失效前10秒钟发送刷新令牌的请求。

AuthState模型:

@不可变 类AuthState {

// properties
final bool isAuthenticated;
final bool isAuthenticating;
final AuthTokens authTokens;
final String error;
final Future<dynamic> refreshTokenPromise;

// constructor with default
AuthState({
    this.isAuthenticated = false,
    this.isAuthenticating = false,
    this.authTokens,
    this.error,
    this.refreshTokenPromise,
});

}

您的身份验证状态模型可以与上面类似。

AuthToken:

@immutable
class AuthTokens {

  // properties
  final String accessToken;
  final String refreshToken;
  final DateTime tokenExpiryTime;

  // constructor with default
  AuthTokens({
    this.accessToken,
    this.refreshToken,
    this.tokenExpiryTime,
  });
 }

尽管我在这里给出了基于redux的解决方案,但是相同的策略也可以应用于其他任何地方。希望对您有所帮助。

答案 1 :(得分:0)

正如您正确指出的那样,问题在于授权服务器收到太多令牌刷新请求。每个特定的用户只能发送一个刷新请求,并依赖该请求的结果。

Flutter的异步包中有一个方便的类,名为AsyncMemoizer,用于此类情况。 从API参考:

一个类,用于只运行一次异步函数并缓存其结果。

某些功能可能多次运行时使用AsyncMemoizer 为了得到它的结果,但是实际上只需要运行一次 为其效果。要记住异步功能的结果,您可以 在函数外创建备忘录(例如,作为实例) 字段(如果您想记住方法的结果),然后包装 调用runOnce的函数主体。

假设您处理所有令牌请求的应用程序组件为单例,则可以这样缓存令牌请求:

class TokenDataSource {
  AsyncMemoizer<TokenResponse> tokenRequestMemoizer = AsyncMemoizer();
  ...

  @override
  Future<Tokens> verifyAndRefreshTokens() async {

    var tokenResponse = await tokenRequestMemoizer.runOnce(() {
      // run your token request code 
    });
    // once the request is done, reset the memoizer so that future clients don't receive the cached tokens
    tokenRequestMemoizer = AsyncMemoizer(); 

    // return results
  }
}

这将使TokenDataSource的所有客户端等待相同的令牌请求,而不是启动新的令牌请求。