【问题标题】:Angular 8 Intercept call to refresh tokenAngular 8拦截调用刷新令牌
【发布时间】:2020-07-21 20:57:43
【问题描述】:

如果当前访问令牌已过期,我正在尝试刷新访问令牌。

我一次发送多个请求,我想做一个队列,所以其他请求不会请求刷新令牌路由。

我在谷歌上搜索了一些最佳实践和示例,并找到了以下针对 Angular 6 和 rxjs v6 的解决方案,它使用了 BehaviourSubject 和 switchMaps。 (请看附件代码)

但是我使用的是 Angular 8 (8.1) 和 rxjs v6.4,这个解决方案对我不起作用。

它根本无法到达this.authService.requestAccessToken().pipe 中的switchMap。 (使用 console.log 测试)

但是,如果我评论 return this.refreshTokenSubject.pipe 并返回 next.handle(request),它会到达那个 switchMap,但我的其他请求都失败了。

您知道是否有任何更改或我应该尝试以其他方式进行更改吗?

  • 令牌拦截器
    import { Injectable } from '@angular/core';
    import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
    import { AuthService } from './auth.service';
    import { Observable, BehaviorSubject, Subject } from 'rxjs';
    import { switchMap, take, filter } from 'rxjs/operators';
    @Injectable()
    export class TokenInterceptor implements HttpInterceptor {
        private refreshTokenInProgress = false;
        private refreshTokenSubject: Subject<any> = new BehaviorSubject<any>(null);

        constructor(public authService: AuthService) { }
        intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

            const accessExpired = this.authService.isAccessTokenExpired();
            const refreshExpired = this.authService.isRefreshTokenExpired();

            if (accessExpired && refreshExpired) {
                return next.handle(request);
            }
            if (accessExpired && !refreshExpired) {
                if (!this.refreshTokenInProgress) {
                    this.refreshTokenInProgress = true;
                    this.refreshTokenSubject.next(null);
                    return this.authService.requestAccessToken().pipe(
                        switchMap((authResponse) => {
                            this.authService.saveToken(AuthService.TOKEN_NAME, authResponse.accessToken);
                            this.authService.saveToken(AuthService.REFRESH_TOKEN_NAME, authResponse.refreshToken);
                            this.refreshTokenInProgress = false;
                            this.refreshTokenSubject.next(authResponse.refreshToken);
                            return next.handle(this.injectToken(request));
                        }),
                    );
                } else {
                    return this.refreshTokenSubject.pipe(
                        filter(result => result !== null),
                        take(1),
                        switchMap((res) => {
                            return next.handle(this.injectToken(request))
                        })
                    );
                }
            }

            if (!accessExpired) {
                return next.handle(this.injectToken(request));
            }
        }

        injectToken(request: HttpRequest<any>) {
            const token = this.authService.getToken(AuthService.TOKEN_NAME);
            return request.clone({
                setHeaders: {
                    Authorization: `Bearer ${token}`
                }
            });
        }
    }
  • requestAccessToken
    requestAccessToken(): Observable<any> {
        const refreshToken = this.getToken(AuthService.REFRESH_TOKEN_NAME);
        return this.http.post(`${this.basePath}/auth/refresh`, { refreshToken });
    }

UPD 1

所以我使用这些资源来编写我的拦截器:

UPD 2

我已经从拦截器范围中排除了 refresh 请求,现在它正在工作 感谢@JBNizet

【问题讨论】:

  • 我的猜测是 authService 发送一个 http 请求以获取刷新的令牌。所以这个http请求被这个拦截器拦截了。并且由于正在进行刷新,因此在刷新令牌之前无法发送用于获取刷新令牌的请求。您不应该像其他请求一样过滤尝试刷新令牌的请求。
  • @JBNizet 刚刚排除了该请求,似乎现在一切正常,非常感谢,你让我很开心。有时我们需要重新审视我们的代码。
  • 你应该用解释和用于修复它的代码来回答你自己的问题。

标签: angular rxjs angular8 angular-http-interceptors refresh-token


【解决方案1】:

我已经从拦截器范围中排除了刷新请求,现在它正在工作。 我做了一个临时修复,以查看它以最快的方式工作。

现在我的 TokenInterceptor 看起来像:

import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
import { AuthService } from './auth.service';
import { Observable, BehaviorSubject, Subject } from 'rxjs';
import { switchMap, take, filter } from 'rxjs/operators';
@Injectable()
export class TokenInterceptor implements HttpInterceptor {
    private refreshTokenInProgress = false;
    private refreshTokenSubject: Subject<any> = new BehaviorSubject<any>(null);

    constructor(public authService: AuthService) { }
    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        if (request.url.indexOf('refresh') !== -1) {
            return next.handle(request);
        }

        const accessExpired = this.authService.isAccessTokenExpired();
        const refreshExpired = this.authService.isRefreshTokenExpired();

        if (accessExpired && refreshExpired) {
            return next.handle(request);
        }
        if (accessExpired && !refreshExpired) {
            if (!this.refreshTokenInProgress) {
                this.refreshTokenInProgress = true;
                this.refreshTokenSubject.next(null);
                return this.authService.requestAccessToken().pipe(
                    switchMap((authResponse) => {
                        this.authService.saveToken(AuthService.TOKEN_NAME, authResponse.accessToken);
                        this.authService.saveToken(AuthService.REFRESH_TOKEN_NAME, authResponse.refreshToken);
                        this.refreshTokenInProgress = false;
                        this.refreshTokenSubject.next(authResponse.refreshToken);
                        return next.handle(this.injectToken(request));
                    }),
                );
            } else {
                return this.refreshTokenSubject.pipe(
                    filter(result => result !== null),
                    take(1),
                    switchMap((res) => {
                        return next.handle(this.injectToken(request))
                    })
                );
            }
        }

        if (!accessExpired) {
            return next.handle(this.injectToken(request));
        }
    }

    injectToken(request: HttpRequest<any>) {
        const token = this.authService.getToken(AuthService.TOKEN_NAME);
        return request.clone({
            setHeaders: {
                Authorization: `Bearer ${token}`
            }
        });
    }
}

感谢@JBNizet

【讨论】:

  • 能否请您提供 accessExpired 您在身份验证服务中的实现方式
  • 我也遇到了同样的问题,花了很多宝贵的时间。最后,在排除“刷新”api之后,它现在可以正常工作了。谢谢你的帖子。
  • 谢谢你。这对我帮助很大!
  • @AntGrisha 先生,您能否也分享一下 isAccessTokenExpired 函数?
  • @KapilSoni,当然,所以基本上我只是在登录期间将令牌的到期时间保存到本地存储(unix 格式)。我的 isAccessTokenExpired 函数看起来像 return moment().utc().isAfter(moment.unix(parseInt(localStorage.getItem('EXP_TIME'))));
【解决方案2】:

@AntGrisha,感谢您在更正后发布解决方案。为我节省了很多工作。我也添加了错误处理。可能会帮助像我这样的初学者,所以在这里发布。我使用了 Angular 9.1.5 版本。

应该像下面这样在请求处理程序中使用管道。

return next.handle(this.injectToken(req)).pipe(
            catchError(this.handleError)

错误处理函数定义

injectToken(request: HttpRequest<any>) {
    const token = this.authService.getToken(AuthService.TOKEN_NAME);
    return request.clone({
        setHeaders: {
            Authorization: `Bearer ${token}`
        }
    });
}
//Error handling function
handleError(error: HttpErrorResponse){
console.log('Error Occurred: '+error);
if(error.status==401){
  return throwError("Unauthorized!");
}
else{
return throwError(error);
 }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-01-21
    • 2017-12-31
    • 2017-12-26
    • 2018-06-12
    • 1970-01-01
    • 2019-09-03
    • 2019-03-28
    • 2020-05-12
    相关资源
    最近更新 更多