我使用Dart在Flutter中编写了一个简单的应用程序。我使用JWT令牌来认证用户。主令牌仅在60秒内有效。 当用户发送带有过期令牌的请求时,webapi返回401。 然后在我的Dart代码中,检查响应的statuscode是否为401 如果是,那么我向RefreshToken端点发送一个请求,然后再发送一次请求(此请求较早返回401)。
如果用户执行许多操作的速度过快,则过期的令牌会多次更新。 我想避免这种情况。 当令牌正在刷新时,完美的实现是,其他请求应等待。
答案 0 :(得分:1)
我遇到了类似的问题,并尝试使用以下方法解决。
我使用flutter-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的解决方案,但是相同的策略也可以应用于其他任何地方。希望对您有所帮助。
答案 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的所有客户端等待相同的令牌请求,而不是启动新的令牌请求。