我正在开发一个Ionic应用程序,当用户在HTTP请求上获得401响应时,尝试兑现刷新令牌。我发现了一些在网上浮动的例子,能够让这个(https://www.intertech.com/Blog/angular-4-tutorial-handling-refresh-token-with-new-httpinterceptor/)工作,除了一次进入的多个请求。
我遇到的问题是一系列调用中的第一个调用调用刷新令牌并成功重试,而其他调用永远不会重试。如果我将.filter和.take从主题返回中获取已经进行刷新的请求,则会重试调用但没有新令牌。当谈到可观察的和主题时,我是新的,所以我不确定问题是什么。
要求
this.myService.getData().subscribe(response => {this.data = response.data;});
this.myService.getMoreData().subscribe(response => {this.moreData = response.data;});
this.myService.getEvenMoreData().subscribe(response => {this.evenMoreData = response.data;});
拦截器
@Injectable()
export class HttpInterceptor implements HttpInterceptor {
isRefreshingToken: boolean = false;
tokenSubject = new BehaviorSubject<string>(null);
tokenService: tokenService;
constructor(private authService: AuthService, private injector: Injector) { }
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<any> {
return this.authService.getUser().flatMap(user => {
request = this.addToken(request, next, user.accessToken);
return next
.handle(request)
.catch(error => {
if (error instanceof HttpErrorResponse) {
switch ((<HttpErrorResponse>error).status) {
case 401:
return this.handle401(request, next, user);
}
} else {
return Observable.throw(error);
};
})
});
}
addToken(request: HttpRequest<any>, next: HttpHandler, accessToken: string): HttpRequest<any> {
return request.clone({ setHeaders: { Authorization: 'Bearer ' + accessToken }})
}
handle401(request: HttpRequest<any>, next: HttpHandler, user: any) {
if (!this.isRefreshingToken) {
this.isRefreshingToken = true;
this.tokenSubject.next(null);
this.tokenService = this.injector.get(tokenService);
return this.tokenService.refresh(user.refreshToken)
.switchMap(refreshResponse => {
if (refreshResponse) {
this.authService.setUser(refreshResponse.id_token, refreshResponse.access_token, refreshResponse.refresh_token);
this.tokenSubject.next(refreshResponse.accessToken);
return next.handle(this.addToken(request, next, refreshResponse.access_token));
}
else {
//no token came back. probably should just log user out.
}
})
.finally(() => {
this.isRefreshingToken = false;
});
}
else {
return this.tokenSubject
.filter(token => token != null)
.take(1)
.switchMap(token => {
return next.handle(this.addToken(request, next, token));
});
}
}
}
它看起来像你没有正确的令牌:
this.tokenSubject.next(refreshResponse.accessToken);
this.tokenSubject.next(refreshResponse.access_token);
我实际上是通过将主题移动到我的身份验证服务并在setUser方法中执行下一步来解决此问题。然后在我的401方法的else语句中,我从我的auth服务上的一个新方法返回了主题并修复了它。我仍然需要take(1)但是能够摆脱过滤器,因为我最终没有使用BehaviorSubject。
我过去遇到过类似的问题。由于某些未知原因(至少对我来说),当我拦截401时,我进行刷新并重试,但重试操作会被取消。
然而,我意识到我可以在客户端读取JWT到期,所以我通过保存令牌到期时间来欺骗系统。然后我做了路由事件(比如onViewWillEnter
)检查过期,如果令牌过期,则刷新它。
这种机制对用户完全透明,如果用户在没有执行HTTP请求的情况下保持太长时间,则确保身份验证令牌或刷新令牌到期,最重要的是,减少延迟,因为您从未获得401响应(在您的方案中,转换为三个要求)。
实现这一目标的一个简单方法是通过一名警卫:
canActivate(route: ActivatedRouteSnapshot,
state: RouterStateSnapshot) {
if (this.refreshTokenService.isExpired) {
this.tokenEvent_.next();
return false;
} else {
this.refreshTokenService.refresh();
}
其中refreshTokenService
是一个具有令牌的实用程序服务,以及一种通过HTTP执行刷新的方法。 tokenEvent
是一个rxjs/Subject
:它在guard构造函数中订阅,每次新事件发生时,它都会重定向到登录页面。
在每个路由上添加此保护可确保令牌始终未过期。