Angular 4 JWT Http 拦截器对链请求不起作用
Angular 4 JWT Http Interceptor doesn't work on chain requests
我已经为新的 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);
});
}
Headers:
private jwtHeaders() {
let currentUser = JSON.parse(localStorage.getItem('currentUser'));
return {headers: new HttpHeaders().set('Authorization', 'Bearer ' + currentUser.token)}
}
}
每当我使用超过 2 个解析器访问路由时,我收到的第一个响应是正确的,并且 returns 用户 object 的刷新令牌和下一个 returns令牌被列入黑名单。能否请您提出可能是什么问题,我花了太多时间来解决这个问题(
更新 1:
我注意到第二个刷新请求传递的是旧令牌而不是新令牌,这就是 Laravel 将令牌列入黑名单的原因
注入 headers 的方式有些 "strange",请尝试:
let httpHeaders = new HttpHeaders()
.set('Authorization', `Bearer ${data.token}`)
const cloneRequest = request.clone({ headers: httpHeaders });
return next.handle(cloneRequest );
我最后一次尝试。如果 headers 发生变化,请尝试检查导航器 :(
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);
});
}
已解决并与多个解析器完美配合:
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);
}
我已经为新的 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);
});
}
Headers:
private jwtHeaders() {
let currentUser = JSON.parse(localStorage.getItem('currentUser'));
return {headers: new HttpHeaders().set('Authorization', 'Bearer ' + currentUser.token)}
}
}
每当我使用超过 2 个解析器访问路由时,我收到的第一个响应是正确的,并且 returns 用户 object 的刷新令牌和下一个 returns令牌被列入黑名单。能否请您提出可能是什么问题,我花了太多时间来解决这个问题(
更新 1:
我注意到第二个刷新请求传递的是旧令牌而不是新令牌,这就是 Laravel 将令牌列入黑名单的原因
注入 headers 的方式有些 "strange",请尝试:
let httpHeaders = new HttpHeaders()
.set('Authorization', `Bearer ${data.token}`)
const cloneRequest = request.clone({ headers: httpHeaders });
return next.handle(cloneRequest );
我最后一次尝试。如果 headers 发生变化,请尝试检查导航器 :(
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);
});
}
已解决并与多个解析器完美配合:
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);
}