面试题答案
一键面试实现思路
- 创建一个HTTP请求拦截器,在
intercept
方法中捕获401
错误。 - 当捕获到
401
错误时,触发刷新令牌请求。 - 刷新令牌成功后,将新的令牌存储起来,并重新发起原请求。
- 为避免多次刷新令牌导致的竞态条件,使用一个标志位和一个队列来管理请求。
关键代码
import { Injectable } from '@angular/core';
import {
HttpEvent,
HttpHandler,
HttpInterceptor,
HttpRequest,
HttpResponse
} from '@angular/common/http';
import { Observable, catchError, finalize, switchMap } from 'rxjs';
import { TokenService } from './token.service';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
private isRefreshing = false;
private refreshTokenSubject: any = new Observable();
constructor(private tokenService: TokenService) {}
intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
request = this.addToken(request);
return next.handle(request).pipe(
catchError((error) => {
if (error.status === 401) {
return this.handle401Error(request, next);
}
return Observable.throwError(error);
})
);
}
private addToken(request: HttpRequest<any>): HttpRequest<any> {
const token = this.tokenService.getToken();
if (token) {
return request.clone({
setHeaders: {
Authorization: `Bearer ${token}`
}
});
}
return request;
}
private handle401Error(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
if (!this.isRefreshing) {
this.isRefreshing = true;
this.refreshTokenSubject = this.tokenService.refreshToken().pipe(
switchMap((newToken: string) => {
this.tokenService.setToken(newToken);
this.isRefreshing = false;
return next.handle(this.addToken(request));
}),
catchError((refreshError) => {
this.isRefreshing = false;
// 处理刷新令牌失败,例如重定向到登录页
return Observable.throwError(refreshError);
}),
finalize(() => {
this.refreshTokenSubject = new Observable();
})
);
}
return this.refreshTokenSubject.pipe(
switchMap(() => {
return next.handle(this.addToken(request));
})
);
}
}
假设TokenService
是一个服务,用于管理令牌,示例代码如下:
import { Injectable } from '@angular/core';
@Injectable()
export class TokenService {
private tokenKey = 'auth_token';
getToken(): string | null {
return localStorage.getItem(this.tokenKey);
}
setToken(token: string): void {
localStorage.setItem(this.tokenKey, token);
}
refreshToken(): Observable<string> {
// 实际实现中,这里应发起刷新令牌的HTTP请求
// 这里简单返回一个模拟的新令牌
return new Observable((observer) => {
const newToken = 'new_refreshed_token';
observer.next(newToken);
observer.complete();
});
}
}
在app.module.ts
中注册拦截器:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform - browser';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { AppComponent } from './app.component';
import { AuthInterceptor } from './auth.interceptor';
import { TokenService } from './token.service';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, HttpClientModule],
providers: [
TokenService,
{
provide: HTTP_INTERCEPTORS,
useClass: AuthInterceptor,
multi: true
}
],
bootstrap: [AppComponent]
})
export class AppModule {}