我已经通过引用此链接Reference Link使用拦截器实现了refreshtoken逻辑。 它按预期工作正常。但在这种情况下,我面临一个关键问题,即当我并行调用多个 api 并且所有 api 返回 401 时,refreshtoken 调用不止一次,具体取决于 this.isRefreshing 变量值设置为 true 的速度。现在我的问题是,我只想调用 refreshtoken api 一次,必须调用所有其他待处理的 api(已返回 401)。如何实现?
有3个api调用。全部返回 401,因此所有人都将尝试调用 refreshtoken。只应调用一个 refreshtoken api,所有其他红色 api 应一一或并行提供。
以下是我的interceptor.ts.
import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from "@angular/common/http";
import { Injectable, Injector } from "@angular/core";
import { BehaviorSubject, Observable, throwError } from "rxjs";
import { catchError, filter, switchMap, take } from "rxjs/operators";
import { AuthenticationService, CommonService } from "../services";
import { TokenService } from "../services/token.service";
/**
* Link Refere : https://stackoverflow.com/questions/57637923/angular-8-intercept-call-to-refresh-token
*/
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
private isRefreshing = false;
private tokenService;
private refreshTokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>(null);
/* No Direct Service inject on Constructor
Using Like:
const authService = this.injector.get(AuthenticationService);
*/
constructor(private injector: Injector) {
this.tokenService = this.injector.get(TokenService);
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
//const token = localStorage.getItem('accessToken');
// const token = this.tokenService.accessToken;
// if (token) {
// req = this.addTokenHeader(req, token);
// }
req = req.clone({
withCredentials: true
});
return next.handle(req).pipe(catchError(error => {
// if (error instanceof HttpErrorResponse && !req.url.includes('auth/signin') && error.status === 401) {
// return this.handle401Error(req, next);
// }
if (error instanceof HttpErrorResponse && error.status === 401) {
return this.handle401Error(req, next);
}
return throwError(error);
}));
}
private handle401Error(request: HttpRequest<any>, next: HttpHandler) {
console.log("401 error request:", request);
const authService = this.injector.get(AuthenticationService);
if (!this.isRefreshing) {
this.isRefreshing = true;
this.refreshTokenSubject.next(null);
return authService.refreshToken().pipe(
switchMap((token: any) => {
this.isRefreshing = false;
this.tokenService.accessToken = token.accessToken;
this.refreshTokenSubject.next(token.accessToken);
return next.handle(request);
//return next.handle(this.addTokenHeader(request, token.accessToken));
}),
catchError((err) => {
this.isRefreshing = false;
authService.directRedirectToLogin();
// authService.logout().subscribe(data => {
// authService.redirectToLogin();
// });
return throwError(err);
})
);
}
return this.refreshTokenSubject.pipe(
filter(token => token !== null),
take(1),
switchMap((token) => next.handle(request))
//switchMap((token) => next.handle(this.addTokenHeader(request, token)))
);
}
private addTokenHeader(request: HttpRequest<any>, token: string) {
const setHeaders = {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json'
}
if (request.body instanceof FormData) {
delete setHeaders["Content-Type"];
}
return request.clone({
setHeaders: setHeaders
});
}
}
更新 02.12.2022: 这是一个很好的例子! GitHub
它使用的服务将等待刷新令牌(或刷新时出错)!试试这个!
这是我在这种情况下的拦截器:
import { Injectable } from '@angular/core';
import {
HttpRequest,
HttpHandler,
HttpEvent,
HttpInterceptor,
HttpErrorResponse,
} from '@angular/common/http';
import {
BehaviorSubject,
catchError,
filter,
Observable,
switchMap,
take,
throwError,
} from 'rxjs';
import { AuthService } from '../services/auth.service';
@Injectable()
export class BearerInterceptor implements HttpInterceptor {
private isRefreshing = false;
private refreshTokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>(
null
);
constructor(private authService: AuthService) {}
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<Object>> {
let authReq = req;
const token = this.authService.getToken();
if (token != null) {
authReq = this.addTokenHeader(req, token);
}
return next.handle(authReq).pipe(
catchError((error) => {
if (
error instanceof HttpErrorResponse &&
!authReq.url.includes('auth/signin') &&
error.status === 401
) {
if (authReq.url.includes('auth/token'))
return throwError(() => new Error(error.error));
return this.handle401Error(authReq, next);
}
return throwError(() => new Error(error));
})
);
}
private handle401Error(request: HttpRequest<any>, next: HttpHandler) {
if (!this.isRefreshing) {
this.isRefreshing = true;
this.refreshTokenSubject.next(null);
const token = this.authService.getRefreshToken();
if (token)
return this.authService.refreshToken(token).pipe(
switchMap((token: any) => {
this.isRefreshing = false;
this.authService.saveTokens(token.token, token.refreshToken);
this.refreshTokenSubject.next(token.accessToken);
return next.handle(this.addTokenHeader(request, token.token));
}),
catchError((err) => {
this.isRefreshing = false;
this.authService.signOut();
return throwError(() => new Error(err));
})
);
}
return this.refreshTokenSubject.pipe(
filter((token) => token !== null),
take(1),
switchMap((token) => next.handle(this.addTokenHeader(request, token)))
);
}
private addTokenHeader(request: HttpRequest<any>, token: string) {
/* for Spring Boot back-end */
// return request.clone({ headers: request.headers.set(TOKEN_HEADER_KEY, 'Bearer ' + token) });
/* for Node.js Express back-end */
return request.clone({
headers: request.headers.set('Authorization', 'Bearer ' + token),
});
}
}
我认为重要的部分是检查错误来自哪里:
if (error instanceof HttpErrorResponse &&
!authReq.url.includes('auth/signin') &&
error.status === 401) {
if (authReq.url.includes('auth/token'))
return throwError(() => new Error(error.error));
return this.handle401Error(authReq, next);
}
所以包括 url auth/token 错误来自令牌。否则全部保留并等待刷新令牌。
玩得开心,弗洛里安
试试这个,我也遇到了同样的问题,但现在使用它工作正常。
import { Injectable } from '@angular/core';
import {
HttpEvent,
HttpHandler,
HttpInterceptor,
HttpRequest
} from '@angular/common/http';
import { throwError, Observable, BehaviorSubject } from 'rxjs';
import { catchError, concatMap, filter, finalize, take } from 'rxjs/operators';
import { environment } from '@app/env';
import { AuthService, RefreshTokenResult } from '../auth.service';
@Injectable()
export class AuthIntercepter implements HttpInterceptor {
isRefreshingToken = false;
tokenRefreshed$ = new BehaviorSubject<boolean>(false);
constructor(private authService: AuthService) {}
addToken(req: HttpRequest<any>): HttpRequest<any> {
const token = this.authService.token;
return token
? req.clone({ setHeaders: { Authorization: 'Bearer ' + token } })
: req;
}
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
return next.handle(this.addToken(req)).pipe(
catchError((err) => {
if (err.status === 401) {
return this.handle401Error(req, next);
}
return throwError(err);
})
);
}
private handle401Error(
req: HttpRequest<any>,
next: HttpHandler
): Observable<any> {
if (this.isRefreshingToken) {
return this.tokenRefreshed$.pipe(
filter(Boolean),
take(1),
concatMap(() => next.handle(this.addToken(req)))
);
}
this.isRefreshingToken = true;
// Reset here so that the following requests wait until the token
// comes back from the refreshToken call.
this.tokenRefreshed$.next(false);
return this.authService.refreshToken().pipe(
concatMap((res: RefreshTokenResult) => {
if (!environment.production) {
console.info('Token was successfully refreshed'); // tslint:disable-line
}
this.tokenRefreshed$.next(true);
return next.handle(this.addToken(req));
}),
catchError((err) => {
this.authService.logout();
return throwError(err);
}),
finalize(() => {
this.isRefreshingToken = false;
})
);
}
}