1

我已经为新的 HttpClient 实现了 http 拦截器,并且一切正常,令牌会针对单个请求进行刷新,但是如果我尝试访问从两个 api 延迟加载数据的路由,我会收到一个错误,并且我的 JWT 令牌被列入黑名单。

Laravel 后端令牌刷新方法:

public function refreshToken() {

        $token = \JWTAuth::getToken();

        if (! $token) {
            return response()->json(["error" => 'Token is invalid'], 401);
        }

        try {

            $refreshedToken = \JWTAuth::refresh($token);
            $user = \JWTAuth::setToken($refreshedToken)->toUser();

        } catch (JWTException $e) {

            return response()->json(["error" => $e->getMessage()], 400);
        }

        return response()->json(["token" => $refreshedToken, "user" => $user], 200);
    }

Angular Http 拦截器:

@Injectable()
export class RefreshTokenInterceptor implements HttpInterceptor {

    constructor(private injector: Injector) { }

    intercept(request: HttpRequest<any>, next: HttpHandler) : Observable<HttpEvent<any>> {

        return next.handle(request).catch((errorResponse: HttpErrorResponse) => {

            const error = (typeof errorResponse.error !== 'object') ? JSON.parse(errorResponse.error) : errorResponse;

            if(errorResponse.status === 401 && error.error === 'token_expired') {

                const http = this.injector.get(HttpClient);

                let token = localStorage.getItem('token');

                return http.post<any>(`${environment.apiBaseUrl}token/refresh`, {},
                    {headers: new HttpHeaders().set('Authorization', `Bearer ${token}`)})
                    .flatMap(data => {

                        localStorage.setItem('currentUser', JSON.stringify(data));
                        localStorage.setItem('token', data.token);

                        const cloneRequest = request.clone({setHeaders: {'Authorization': `Bearer ${data.token}`}});
                        return next.handle(cloneRequest);
                    });
            }

            return Observable.throw(errorResponse);
        });
    }
}

我使用解析器的路线:

{
        path: '',
        children: [ {
            path: 'create',
            component: CreateCarComponent,
            resolve: {
                subcategories: SubCategoriesResolver,
                companies: CompaniesResolver
            }
        }]
    }

公司解析器:(汽车解析器与此类似)

@Injectable()
export class CompaniesResolver implements Resolve<any> {

    constructor(private _userService: UserService) {}

    resolve(route: ActivatedRouteSnapshot) {
        return this._userService.getCompaniesList();
    }
}

用户服务方法示例:

getUserCardsApi: string = "user/cars/all";

    getCardsList() :  Observable<any[]> {

        return this._http.get(environment.apiBaseUrl + this.getUserCardsApi, this.jwtHeaders())
            .catch(error => {

                return Observable.throw(error);
            });
    }

标题:

private jwtHeaders() {

        let currentUser = JSON.parse(localStorage.getItem('currentUser'));

        return {headers: new HttpHeaders().set('Authorization', 'Bearer ' + currentUser.token)}
        }
    }

每当我使用超过 2 个解析器访问路由时,我收到的第一个响应是正确的,并返回一个带有用户对象的刷新令牌,之后的下一个响应立即返回被列入黑名单的令牌。您能否建议可能是什么问题,我花了太多时间来解决这个问题(

更新1:

我注意到第二次刷新请求传递的是旧令牌而不是新令牌,这就是 Laravel 将令牌列入黑名单的原因

4

3 回答 3

0

已解决并与多个解析器完美配合:

export class RefreshTokenInterceptor implements HttpInterceptor {

isRefreshingToken: boolean = false;
    tokenSubject: BehaviorSubject<string> = new BehaviorSubject<string>(null);

    constructor(private router: Router, private injector: Injector, private _loadingBar: SlimLoadingBarService) {

    }

    addToken(req: HttpRequest<any>, token: string): HttpRequest<any> {
        return req.clone({ setHeaders: { Authorization: `Bearer ${token}`}})
    }

    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpSentEvent | HttpHeaderResponse | HttpProgressEvent | HttpResponse<any> | HttpUserEvent<any>> {

        return next.handle(this.addToken(req, localStorage.getItem('token')))

            .catch(error => {

                if (error instanceof HttpErrorResponse) {

                    switch ((<HttpErrorResponse>error).status) {
                        case 400:
                            return this.handle400Error(error);
                        case 401:
                            return this.handle401Error(req, next);
                    }

                } else {

                    return Observable.throw(error);
                }
            });
    }

    handle401Error(req: HttpRequest<any>, next: HttpHandler) {

        if (!this.isRefreshingToken) {
            this.isRefreshingToken = true;

            // Reset here so that the following requests wait until the token
            // comes back from the refreshToken call.
            this.tokenSubject.next(null);
            let token = localStorage.getItem('token');
            const http = this.injector.get(HttpClient);

            return http.post<any>(`${environment.apiBaseUrl}token/refresh`, {},
                {headers: new HttpHeaders().set('Authorization', `Bearer ${token}`)})
                .switchMap((data: string) => {

                if (data["token"]) {
                        this.tokenSubject.next(data["token"]);
                        return next.handle(this.addToken(req, data["token"]));
                    }

                    // If we don't get a new token, we are in trouble so logout.
                    return this.logoutUser();
                })
                .catch(error => {
                    // If there is an exception calling 'refreshToken', bad news so logout.
                    return this.logoutUser();
                })
                .finally(() => {
                    this.isRefreshingToken = false;
                });

        } else {

            return this.tokenSubject
                .filter(token => token != null)
                .take(1)
                .switchMap(token => {
                    return next.handle(this.addToken(req, token));
                });
        }
    }

    logoutUser() {
        // Route to the login page (implementation up to you)
        localStorage.removeItem('currentUser');
        localStorage.removeItem('token');

        this.router.navigate(['./auth/login']);

        return Observable.throw("Error Logout");
    }

    handle400Error(error) {
        if (error && error.status === 400 && error.error && error.error.error === 'invalid_grant') {
            // If we get a 400 and the error message is 'invalid_grant', the token is no longer valid so logout.
            return this.logoutUser();
        }

        return Observable.throw(error);
    }
于 2017-12-12T18:39:30.353 回答
0

我的最后一次尝试。如果标题发生变化,请尝试签入导航器:(

if(errorResponse.status === 401 && error.error === 'token_expired') {
      const http = this.injector.get(HttpClient);
      let token = localStorage.getItem('token');
      return http.post(`${environment.apiBaseUrl}token/refresh`, {},
          {headers: new HttpHeaders().set('Authorization', `Bearer ${token}`)})
                 .switchMap(data => {
                        localStorage.setItem('currentUser', JSON.stringify(data));
                        localStorage.setItem('token', data.token);
                        const cloneRequest = request.clone(
                          {headers: new HttpHeaders()
                              .set('Authorization', `Bearer ${data.token}`)
                          });
                        return next.handle(cloneRequest);
                    });
            }
于 2017-12-12T09:05:54.307 回答
0

注入标头的方式有些“奇怪”,请尝试:

let httpHeaders = new HttpHeaders()
              .set('Authorization', `Bearer ${data.token}`)
            const cloneRequest = request.clone({ headers: httpHeaders });
            return next.handle(cloneRequest );
于 2017-12-11T16:06:53.940 回答