带有 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 集合映射