angular - 防止在 Angular 中多次触发令牌刷新请求
问题描述
我正在使用 JWT 构建一个基本的身份验证系统。以下是基本工作流程:
在向 API 发送请求之前,我有一个 HttpInterceptor 来检查令牌是否已过期。如果它还没有过期,拦截器将使用 JWT 将授权标头附加到请求中。如果它已过期,则需要在发送实际请求之前刷新令牌。当刷新请求到达后端时,它将根据数据库检查刷新令牌并删除条目,使其只能使用一次,然后它将返回一个新的 JWT + 刷新令牌。
我的一些页面在访问它们时会触发多个请求,问题就来了。将一次发送多个引用请求,因此只有第一个到达后端的请求会成功返回。所有其他请求将返回 401 错误,这会导致客户端出现问题。
因此,我正在寻找一种方法来暂停所有请求,直到第一个刷新请求返回。HttpInterceptor 调用一个函数,该函数通过检查过期日期并在过期时触发刷新请求来返回 JWT。
令牌拦截器:
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// Get access token
this._auth.getAccessToken().subscribe((accessToken: string) => {
request = request.clone({
setHeaders: {
Authorization: `Bearer ${accessToken}`
}
});
return next.handle(request);
});
}
获取访问令牌功能:
public getAccessToken(): Observable<string> {
if (!this._authUser) return throwError("User is not logged in."); // Make sure user is logged in
// Refresh token
if (this.checkTokenExpired())
return this.refreshToken().pipe(map(() => this._authUser.tokenManager.accessToken));
return of(this._authUser.tokenManager.accessToken);
}
对于同步请求,此架构成为一个问题,因为刷新请求将被多次发送。我需要类似互斥锁的东西来停止除第一个请求之外的所有请求,并在刷新令牌后释放所有请求并返回新的 JWT。
解决方案
感谢以下帖子,我能够获得我想要的实现:https ://stackoverflow.com/a/54328099/5203853
这就是我的refreshToken()
函数实现的样子:
// Variables
private _tokenSubject: BehaviorSubject<auth.User> = new BehaviorSubject<auth.User>(null);
private _isRefreshingToken: Boolean = false;
private refreshToken(): Observable<auth.User> {
if (!this._authUser) return throwError("User is not logged in."); // Make sure user is logged in
if (!this._isRefreshingToken) {
this._isRefreshingToken = true;
// Reset such that the following requests wait until the token
// comes back from the refreshToken call.
this._tokenSubject.next(null);
let req = this._httpClient.post<AuthService.authUser>('/api/auth/refresh', {
accessToken: this._authUser.tokenManager.accessToken,
refreshToken: this._authUser.tokenManager.refreshToken
})
return req.pipe(
tap(authUser => {
this.saveUser(authUser);
// Emit event & return promise
this.onAuthStateChange.emit(authUser.user);
// Retry previous request
this._tokenSubject.next(authUser.user);
}),
map(authUser => authUser.user),
catchError((err: HttpErrorResponse) => {
this.signOut();
this._router.navigate(['/auth/login']);
return throwError(err.message);
}),
finalize(() => {
this._isRefreshingToken = false;
})
);
}
else {
return this._tokenSubject
.pipe(
filter(authUser => authUser != null),
take(1)
);
}
}
推荐阅读
- python - 如果值为 NaN 但维护数据框中的其余数据,python 中是否有任何函数可以擦除特定列
- javascript - 使用 JavaScript 的表单验证不适用于 PHP
- google-apps-script - 将信息从 Google 表单自动填充到表格
- offline - 如何在 Suse12 SP3 上离线安装 kubeadm kubelet kubectl
- python - 正则表达式 - 无法在 ' 之后提取单词
- android - Android:如何让我的应用程序从广播接收器和警报在屏幕上打开
- swift - CloudKit:不在属性初始化器中使用实例成员“谓词”;属性初始化程序在“自我”可用之前运行
- c# - 使用 Google API 在服务器端获取用户信息
- security - 刷新令牌轮换 - 真的足够了吗?
- r - 从生存包生成风险表