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

Posted

技术标签:

【中文标题】带有 ASP.NET Core WebAPI 的 Dart 中的令牌刷新并发问题【英文标题】:Token refresh concurrency issue in Dart with ASP.NET Core WebAPI 【发布时间】:2019-03-12 18:09:00 【问题描述】:

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

如果用户做很多动作太快,过期的令牌会被更新很多次。 我想避免这种情况。 在刷新令牌时完美的灵魂,其他请求应该等待。

【问题讨论】:

【参考方案1】:

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

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

    登录后获取 jwt 令牌 在客户端将 jwt 令牌解码为来自服务器的响应。 它包含一个超时 - 到期时间。 在客户端创建一个 redux 中间件,比如说_createRefreshTokenMiddleware。 来自客户端的每个请求在发送到服务器之前都应该经过这个中间件。 在这个中间件中,对服务器的每个请求,检查令牌超时,如果令牌过期,保持这些请求,向服务器发送请求以刷新令牌,等到收到新令牌,使用这个新令牌发送那些向服务器发出请求。 令牌将过期的所有其他请求都将等待一个共同的承诺,比如refreshTokenPromise 以获取 refreshToken 首先得到解决。这样您就不必发送多个 refreshToken 请求。 如果令牌仍然有效,让请求通过。

参考下面的例子-

您的中间件:

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 的解决方案,但同样的策略也可以应用于其他任何地方。希望对你有帮助。

【讨论】:

【参考方案2】:

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

Flutter 的 async 包有一个名为 AsyncMemoizer 的方便类,用于此类情况。 来自 API 参考:

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

当某个函数可能被多次运行时使用 AsyncMemoizer 为了得到它的结果,但实际上只需要运行一次 因为它的效果。要记住异步函数的结果,您可以 在函数外部创建一个 memoizer(例如作为一个实例 字段,如果你想记住一个方法的结果),然后换行 调用 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 的所有客户端等待相同的令牌请求,而不是启动一个新的请求。

【讨论】:

以上是关于带有 ASP.NET Core WebAPI 的 Dart 中的令牌刷新并发问题的主要内容,如果未能解决你的问题,请参考以下文章

如何从 ASP.NET Core WebAPI 获取 int 值?

使用 JWT 令牌的 ASP.NET Core 网站到 WebApi 身份验证

Blazor 无法连接到 ASP.NET Core WebApi (CORS)

带有 EF Core 的 ASP.NET Core - DTO 集合映射

仅在 IIS 中发布时,在 ASP.net Core 3.1 WebAPI 中启用 CORS 时出错

带有 EF Core 更新实体的 ASP.Net 核心 Web Api 如何