angular - 無法在攔截器中建立訂閱
我正在製作 JWT 攔截器來處理 401 錯誤。我的想法是將所有在 401 發生後的原始請求收集到一個陣列中,然後刷新我的 token,接著在我的請求中添加一個新的標頭。這是代碼:
type CallerRequest = {
subscriber: Subscriber<any>;
failedRequest: HttpRequest<any>;
};
@Injectable()
export class RefreshTokenInterceptor implements HttpInterceptor {
private refreshInProgress: boolean;
private requests: CallerRequest[] = [];
constructor(
public authService: AuthService,
public customersService: CustomersService,
private http: HttpClient
) {
this.refreshInProgress = false;
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (!req.url.includes('api/')) {
return next.handle(req);
}
const observable = new Observable<HttpEvent<any>>((subscriber) => {
const originalRequestSubscription = next.handle(req)
.subscribe((response) => {
subscriber.next(response);
},
(err) => {
if (err.status === 401) {
this.handleUnauthorizedError(subscriber, req);
} else {
subscriber.error(err);
}
},
() => {
subscriber.complete();
});
return () => {
originalRequestSubscription.unsubscribe();
};
});
return observable;
}
private addToken(request: HttpRequest<any>, token: string) {
return request.clone({setHeaders: {'Authorization': `Bearer ${token}`}});
}
private handleUnauthorizedError(subscriber: Subscriber<any>, request: HttpRequest<any>) {
this.requests.push({ subscriber, failedRequest: request });
if (!this.refreshInProgress) {
this.refreshInProgress = true;
console.log('go refresh');
this.authService.refreshToken()
.pipe(
finalize(() => {
this.refreshInProgress = false;
})
)
.subscribe(
result => {
console.log(result); // <-- I cannot get here
this.repeatFailedRequests(this.authService.getAccessToken());
},
err => {
console.log(err); // <-- And cannot get here
}
)
}
}
private repeatFailedRequests(authHeader: string) {
this.requests.forEach((c) => {
const requestWithNewToken = this.addToken(c.failedRequest, authHeader);
this.repeatRequest(requestWithNewToken, c.subscriber);
});
this.requests = [];
}
private repeatRequest(requestWithNewToken: HttpRequest<any>, subscriber: Subscriber<any>) {
this.http.request(requestWithNewToken).subscribe((res) => {
subscriber.next(res);
},
(err) => {
if (err.status === 401) {
this.authService.removeTokens();
}
subscriber.error(err);
},
() => {
subscriber.complete();
});
}
}
看看我的 handleUnauthorizedError 方法。我在那裡無法獲取結果。
順便說一下,這是我的 refreshToken:
refreshToken() {
return this.post('api/v0/jwt/refresh/', {refresh: this.getRefreshToken()})
.pipe(tap((tokens: Tokens) => {
this.storeTokens(tokens);
}));
}
我做錯了什麼,該如何以正確的方式解決這個問題?謝謝!
新增:
如果我嘗試來自這個問題的 JWT 攔截器 Angular 4 Interceptor 在令牌刷新後重試請求(最受歡迎的答案),我也會遇到相同的問題——我的 RefreshToken() 的結果沒有被處理。
答案#1
問題出在在 refreshInProgress 時正確處理 JWT 刷新請求。這裡是完全正常運作的程式碼:
type CallerRequest = {
subscriber: Subscriber<any>;
failedRequest: HttpRequest<any>;
};
@Injectable()
export class AuthTokenInterceptor implements HttpInterceptor {
private refreshInProgress: boolean;
private requests: CallerRequest[] = [];
private refreshJWTReuest: Subscription;
private straightRequests: string[];
constructor(
public authService: AuthService,
public customersService: CustomersService,
private http: HttpClient
) {
this.refreshInProgress = false;
this.straightRequests = ['currency/'];
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (this.straightRequests.some(v => req.url.includes(v))) {
return next.handle(req);
}
if (req.url.includes('api/v0/jwt/create/')) {
this.refreshInProgress = false;
}
const observable = new Observable<HttpEvent<any>>((subscriber) => {
const originalRequestSubscription = next.handle(this.addToken(req, this.authService.getAccessToken()))
.subscribe((response) => {
subscriber.next(response);
},
(err) => {
if (err.status === 401) {
if (req.url.includes('api/v0/jwt/refresh/')) {
this.refreshJWTReuest.unsubscribe();
this.requests = [];
this.authService.logout();
}
this.handleUnauthorizedError(subscriber, req);
} else {
// notification!!!
subscriber.error(err);
}
},
() => {
subscriber.complete();
});
return () => {
originalRequestSubscription.unsubscribe();
};
});
return observable;
}
private addToken(request: HttpRequest<any>, token: string) {
return request.clone({setHeaders: {'Authorization': `Bearer ${token}`}});
}
private handleUnauthorizedError(subscriber: Subscriber<any>, request: HttpRequest<any>) {
if (!this.refreshInProgress) {
this.refreshInProgress = true;
this.refreshJWTReuest = this.authService.refreshToken()
.subscribe(
result => {
this.refreshInProgress = false;
this.repeatFailedRequests(result.access);
},
err => {
this.authService.logout();
console.log(err);
}
);
}
if (!request.url.includes('api/v0/jwt/refresh/')) {
// avoid refresh requests while refreshInProgress
// this solves a small bug after logout()
this.requests.push({ subscriber, failedRequest: request });
}
}
private repeatFailedRequests(authHeader: string) {
this.requests.forEach((c) => {
const requestWithNewToken = this.addToken(c.failedRequest, authHeader);
this.repeatRequest(requestWithNewToken, c.subscriber);
});
this.refreshJWTReuest.unsubscribe();
this.requests = [];
}
private repeatRequest(requestWithNewToken: HttpRequest<any>, subscriber: Subscriber<any>) {
this.http.request(requestWithNewToken).subscribe((res) => {
subscriber.next(res);
},
(err) => {
if (err.status === 401) {
this.authService.logout();
}
subscriber.error(err);
},
() => {
subscriber.complete();
});
}
}
答案#2
我認為問題出在你的攔截方法上,因為你在包裝原始的 Observable,它沒有被正確調用。
試試像這樣的東西:
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (!req.url.includes('api/')) {
return next.handle(req);
}
return next
.handle(req)
.catch((err) => {
if (err.status === 401) {
this.handleUnauthorizedError(req);
} else {
return next.handle(req);
}
})
);
}
