您必须区分所有请求。例如,您不想拦截您的登录请求,也不想拦截刷新令牌请求。 SwitchMap 是你最好的朋友,因为你需要取消一些调用来等待令牌刷新。
因此,您要做的就是首先检查状态 401(未经授权)的错误响应:
return next.handle(this.addToken(req, this.userService.getAccessToken()))
.pipe(catchError(err => {
if (err instanceof HttpErrorResponse) {
// token is expired refresh and try again
if (err.status === 401) {
return this.handleUnauthorized(req, next);
}
// default error handler
return this.handleError(err);
} else {
return observableThrowError(err);
}
}));
在处理未经授权的函数中,您必须刷新令牌并同时跳过所有进一步的请求:
handleUnauthorized (req: HttpRequest<any>, next: HttpHandler): Observable<any> {
if (!this.isRefreshingToken) {
this.isRefreshingToken = true;
// Reset here so that the following requests wait until the token
// comes back from the refreshToken call.
this.tokenSubject.next(null);
// get a new token via userService.refreshToken
return this.userService.refreshToken()
.pipe(switchMap((newToken: string) => {
// did we get a new token retry previous request
if (newToken) {
this.tokenSubject.next(newToken);
return next.handle(this.addToken(req, newToken));
}
// If we don't get a new token, we are in trouble so logout.
this.userService.doLogout();
return observableThrowError('');
})
, catchError(error => {
// If there is an exception calling 'refreshToken', bad news so logout.
this.userService.doLogout();
return observableThrowError('');
})
, finalize(() => {
this.isRefreshingToken = false;
})
);
} else {
return this.tokenSubject
.pipe(
filter(token => token != null)
, take(1)
, switchMap(token => {
return next.handle(this.addToken(req, token));
})
);
}
}
我们在拦截器类上有一个属性,用于检查是否已经有刷新令牌请求正在运行:this.isRefreshingToken = true;
因为当您触发多个未经授权的请求时,您不希望有多个刷新请求。
所以里面的一切if (!this.isRefreshingToken)
部分是关于刷新您的令牌并再次尝试之前的请求。
处理的所有内容else
适用于所有请求,同时,当您的 userService 刷新令牌时,会返回 tokenSubject,并且当令牌准备就绪时this.tokenSubject.next(newToken);
每个跳过的请求都将被重试。
这篇文章是拦截器的最初灵感:https://www.intertech.com/angular-4-tutorial-handling-refresh-token-with-new-httpinterceptor/ https://www.intertech.com/angular-4-tutorial-handling-refresh-token-with-new-httpinterceptor/
EDIT:
TokenSubject实际上是一个行为主体:tokenSubject: BehaviorSubject<string> = new BehaviorSubject<string>(null);
,这意味着任何新订阅者都将获得流中的当前值,这将是我们上次调用时的旧令牌this.tokenSubject.next(newToken)
.
Withnext(null)
每个新订阅者都不会触发switchMap
部分,这就是为什么filter(token => token != null)
是必要的。
After this.tokenSubject.next(newToken)
使用新令牌再次调用每个订阅者都会触发switchMap
与新鲜令牌分开。希望现在更清楚了
编辑2020年9月21日
Fix link