问题
Initially, I had a function that simply checked for the presence of a token and, if it was not present, sent the user to the login header. Now I need to implement the logic of refreshing a token when it expires with the help of a refreshing token. But I get an error 401. The refresh function does not have time to work and the work in the interceptor goes further to the error. How can I fix the code so that I can wait for the refresh to finish, get a new token and not redirect to the login page?
TokenInterceptor
import {HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from "@angular/common/http";
import {Injectable, Injector} from "@angular/core";
import {AuthService} from "../services/auth.service";
import {Observable, throwError} from "rxjs";
import {catchError, tap} from "rxjs/operators";
import {Router} from "@angular/router";
import {JwtHelperService} from "@auth0/angular-jwt";
@Injectable({
providedIn: 'root'
})
export class TokenInterceptor implements HttpInterceptor{
private auth: AuthService;
constructor(private injector: Injector, private router: Router) {}
jwtHelper: JwtHelperService = new JwtHelperService();
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
this.auth = this.injector.get(AuthService);
const accToken = this.auth.getToken();
const refToken = this.auth.getRefreshToken();
if ( accToken && refToken ) {
if ( this.jwtHelper.isTokenExpired(accToken) ) {
this.auth.refreshTokens().pipe(
tap(
() => {
req = req.clone({
setHeaders: {
Authorization: `Bearer ${accToken}`
}
});
}
)
)
} else {
req = req.clone({
setHeaders: {
Authorization: `Bearer ${accToken}`
}
});
}
}
return next.handle(req).pipe(
catchError(
(error: HttpErrorResponse) => this.handleAuthError(error)
)
);
}
private handleAuthError(error: HttpErrorResponse): Observable<any>{
if (error.status === 401) {
this.router.navigate(['/login'], {
queryParams: {
sessionFailed: true
}
});
}
return throwError(error);
}
}
AuthService
import {Injectable} from "@angular/core";
import {HttpClient, HttpHeaders} from "@angular/common/http";
import {Observable, of} from "rxjs";
import {RefreshTokens, Tokens, User} from "../interfaces";
import {map, tap} from "rxjs/operators";
@Injectable({
providedIn: 'root'
})
export class AuthService{
private authToken = null;
private refreshToken = null;
constructor(private http: HttpClient) {}
setToken(authToken: string) {
this.authToken = authToken;
}
setRefreshToken(refreshToken: string) {
this.refreshToken = refreshToken;
}
getToken(): string {
this.authToken = localStorage.getItem('auth-token');
return this.authToken;
};
getRefreshToken(): string {
this.refreshToken = localStorage.getItem('refresh-token');
return this.refreshToken;
};
isAuthenticated(): boolean {
return !!this.authToken;
}
isRefreshToken(): boolean {
return !!this.refreshToken;
}
refreshTokens(): Observable<any> {
const httpOptions = {
headers: new HttpHeaders({
'Authorization': 'Bearer ' + this.getRefreshToken()
})
};
return this.http.post<RefreshTokens>('/api2/auth/refresh', {}, httpOptions)
.pipe(
tap((tokens: RefreshTokens) => {
localStorage.setItem('auth-token', tokens.access_token);
localStorage.setItem('refresh-token', tokens.refresh_token);
this.setToken(tokens.access_token);
this.setRefreshToken(tokens.refresh_token);
console.log('Refresh token ok');
})
);
}
}
回答1:
In your example you never subscribe to your refreshTokens().pipe() code. Without a subscription, the observable won't execute.
回答2:
req = this.auth.refreshTokens().pipe(
switchMap(() => req.clone({
setHeaders: {
Authorization: `Bearer ${this.auth.getToken()}`
}
}))
)
This will first call refreshToken and run the tap there, then emit request with the new this.auth.getToken(), note that accToken still have old value as the code is not rerun.
回答3:
You have to do something like that:
const firstReq= cloneAndAddHeaders(req);
return next.handle(firstReq).pipe(
catchError(
err => {
if (err instanceof HttpErrorResponse) {
if (err.status === 401 || err.status === 403) {
if (firstReq.url === '/api2/auth/refresh')) {
auth.setToken('');
auth.setRefreshToken('');
this.router.navigate(['/login']);
}
else {
return this.auth.refreshTokens().pipe(
mergeMap(() => {
const secondReq = cloneAndAddHeaders(req);
return next.handle(secondReq);
})
);
}
}
return throwError(err.message || 'Server error');
}
}
)
);
The implementation of cloneAndAddHeaders is obvious.
来源:https://stackoverflow.com/questions/52965491/refresh-token-jwt-in-interceptor-angular-6