问题
I have built my AuthInterceptor which on a 401 error sends a request to get a new token.
The handle401Error method is called when I encounter a 401 error, But I don't seem to be getting any response in my this.authService.refreshToken().pipe()
AuthInterceptor
import { Injectable, Injector } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpErrorResponse } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError, tap, switchMap } from 'rxjs/operators';
import { AuthService } from '../services/auth.service';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
isRefreshingToken:boolean = false;
constructor(public authService: AuthService, private injector: Injector) {}
addToken(req: HttpRequest<any>, token: string): HttpRequest<any> {
return req.clone({ setHeaders: {
Authorization: 'Bearer ' + token,
'Content-Type': 'application/json'
}})
}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
request = this.addToken(request, this.authService.getToken())
return next.handle(request).pipe (
catchError((error: Error) => {
if(error instanceof HttpErrorResponse){
console.log('err', error);
switch(error.status) {
case 401:
this.handle401Error(request, next)
default:
return throwError(error);
}
} else {
return throwError(error);
}
})
);
}
handle401Error(request: HttpRequest<any>, next: HttpHandler){
console.log('handle 401');
if(!this.isRefreshingToken) { //do not send another refresh token request if already in progress
this.isRefreshingToken = true;
return this.authService.refreshToken().pipe(
switchMap((newToken: string) => {
console.log('newToken', newToken) // I DO NOT GET A VALUE HERE
if (newToken) {
return next.handle(this.addToken(request, newToken));
}
}),
catchError((error) => {
console.log('error', error) //NOR DO I GET AN ERROR
return throwError(error)
}),
tap(data => console.log(data))
)
}
}
}
AuthService Refresh Token
refreshToken(): Observable<string> {
console.log('refreshToken');
let token = 'xxx-xxx-xx';
return of(token).pipe(delay(1000));
}
回答1:
Besides the missing return in 'case 401', here's how I would handle it.
The basic idea is that you want to retry the entire observable once you receive the new refresh token. Also, the request is patched inside an observable so that on each retry it will get the new token from the auth service.
import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { catchError, retryWhen, switchMap, tap } from 'rxjs/operators';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
private retryRequest = Symbol('reload');
private refreshToken = this.authService.refreshToken()
.pipe(
tap(newToken => {
if (newToken) {
throw this.retryRequest;
}
}),
share(),
) as Observable<any>;
constructor(public authService: AuthService) { }
private addToken(req: HttpRequest<any>, token: string): HttpRequest<any> {
return req.clone({
setHeaders: {
Authorization: 'Bearer ' + token,
'Content-Type': 'application/json'
}
});
}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const request$ = new Observable<HttpRequest<any>>(observer => {
observer.next(this.addToken(request, this.authService.getToken()));
observer.complete();
});
return request$.pipe(
switchMap(req => next.handle(req)),
catchError((error: Error) => {
if (error instanceof HttpErrorResponse) {
switch (error.status) {
case 401:
return this.refreshToken;
default:
throw error;
}
} else {
throw error;
}
}),
retryWhen(err$ =>
err$.pipe(
tap(err => {
if (err === this.retryRequest) { return; }
throw err;
})
)
)
);
}
}
来源:https://stackoverflow.com/questions/52720099/angular-6-http-interceptor-refresh-token-401-handling