简体   繁体   English

Angular 4 JWT Http拦截器不适用于链请求

[英]Angular 4 JWT Http Interceptor doesn't work on chain requests

I've implemented http interceptor for new HttpClient and everything works fine, token is refreshed for single request, but if I try to access the route which lazy load data from two api's I received an error and my JWT token is blacklisted. 我已经为新的HttpClient实现了HTTP拦截器,并且一切正常,令牌针对单个请求进行了刷新,但是如果我尝试访问从两个api延迟加载数据的路由,则会收到错误消息,并且我的JWT令牌已列入黑名单。

Laravel Backend Token Refresh Method: 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 Interceptor: 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);
        });
    }
}

My Route which use resolvers: 使用解析器的“我的路线”:

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

Companies Resolver: (Car resolver is simmilar to this) 公司解析器:(汽车解析器与此类似)

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

    constructor(private _userService: UserService) {}

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

User Service Method Example: 用户服务方法示例:

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

    getCardsList() :  Observable<any[]> {

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

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

Headers: 标头:

private jwtHeaders() {

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

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

Whenever I hit routes with more than 2 resolvers, first response I receive is correct and returns a refreshed token with user object and the next one after that right away returns token blacklisted. 每当我使用2个以上的解析器点击路由时,我收到的第一个响应都是正确的,并返回带有用户对象的刷新令牌,而此后的下一个立即将令牌列入黑名单。 Could you please suggest what can be the issue, I've spent too much time on solving this ( 您能否提出可能的问题,我已经花了很多时间来解决这个问题(

Update 1: 更新1:

What I noticed is that second refresh request is passing an old token rather than a new one thats why Laravel blacklisting a token 我注意到的是第二个刷新请求正在传递旧令牌,而不是新令牌,这就是Laravel将令牌列入黑名单的原因

It's some "strange" the way you inject the headers, try: 注入标头的方式有些“奇怪”,请尝试:

let httpHeaders = new HttpHeaders()
              .set('Authorization', `Bearer ${data.token}`)
            const cloneRequest = request.clone({ headers: httpHeaders });
            return next.handle(cloneRequest );

My last attempt. 我的最后尝试。 Try check in a navigator if headers changes :( 如果标题更改,请尝试在导航器中检查:(

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);
                    });
            }

Solved and perfectly works with multiple resolvers: 已解决,可以与多个解析器完美配合使用:

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);
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM