javascript - Angular 5 HttpInterceptor 请求前刷新令牌
问题描述
我需要在发出请求之前刷新 HttpInterceptor 中的令牌,为此我在请求之前检查访问令牌并在它过期时调用刷新。目前,我的拦截器如下所示:
@Injectable()
export class TokenInterceptor implements HttpInterceptor {
private refreshTokenSubject = new BehaviorSubject(null);
private refreshTokenObservable = this.refreshTokenSubject.asObservable();
private isRefreshingToken = false;
constructor(private authService: AuthService) {
}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const tokenData = AuthService.getCurrentSessionData();
if (!this.isRefreshingToken) {
// if no token set - make request as-is
const tokenSet = tokenData && tokenData.token;
if (!tokenSet) {
return next.handle(request);
}
// proceed if token not expired
const tokenExpired = new Date(tokenData.expirationDate) < new Date();
if (!tokenExpired) {
return next.handle(this.setAuthHeader(request, tokenData.token));
}
// check if we can refresh the token and logout instantly if not
const tokenRefreshable = tokenData.refreshToken && new Date(tokenData.refreshTokenExpirationDate) > new Date();
if (!tokenRefreshable) {
this.authService.logout();
return Observable.throw('');
}
this.isRefreshingToken = true;
// make all subsequent requests wait for new token
this.refreshTokenSubject.next(null);
// make refresh request
return this.authService.refreshToken()
.switchMap((res: any) => {
AuthService.storeSessionData(res, Utils.getLocalStorageItem(STORAGE_KEYS.STAY_LOGGED_IN));
this.isRefreshingToken = false;
// let subsequent awaiting proceed
this.refreshTokenSubject.next(res.access_token);
return next.handle(this.setAuthHeader(request, res.access_token));
})
.catch((err) => {
this.authService.logout();
return Observable.throw('');
})
.finally(() => {
this.isRefreshingToken = false;
});
} else {
// if token refreshing in progress - wait for new token
return this.refreshTokenObservable
.filter(token => token !== null)
.take(1)
.switchMap((token) => {
return next.handle(this.setAuthHeader(request, token));
});
}
}
private setAuthHeader(request, token) {
return request.clone({
setHeaders: {
Authorization: `Bearer ${token}`,
},
});
}
}
问题是this.authService.refreshToken()
永远不会发出请求,并且后续请求永远不会继续。我猜这是因为没有订阅从 HttpClient 返回的 observable,这是refreshToken
方法代码:
public refreshToken() {
const tokenData = AuthService.getCurrentSessionData();
return this.http.post(
`${environment.apiPath}/auth/refresh`,
{ refresh_token: tokenData.refreshToken },
);
}
如何修复此代码以发出refreshToken
请求并让其他请求按预期进行?
解决方案
@Injectable()
export class RequestInterceptorService implements HttpInterceptor {
@BlockUI() blockUI: NgBlockUI;
isRefreshingToken = false;
tokenSubject: BehaviorSubject<string> = new BehaviorSubject<string>(null);
oldToken = localStorage.getItem('access_token');
constructor(private authService: AuthService, private localStorageToken: AppLocalStorage,
private route: ActivatedRoute, private router: Router) {}
addToken(req: HttpRequest<any>, token: string): HttpRequest<any> {
return req.clone({ setHeaders: { Authorization: 'Bearer ' + token, 'Access-Control-Allow-Origin': '*' }});
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpSentEvent | HttpHeaderResponse
| HttpProgressEvent | HttpResponse<any> | HttpUserEvent<any>> {
return next.handle(this.addToken(req, this.authService.getAuthToken()))
.catch(error => {
if (error instanceof HttpErrorResponse) {
switch ((<HttpErrorResponse>error).status) {
case 400:
return this.handle400Error(error);
case 401: this.authService.refresh().subscribe((data) => { // A call has been made at an instant where network get 401 status code,It will prevent the asynchronous way of handling network calls.
this.localStorageToken.setRefreshTokens(data); //localstorageToken is used to store all the response, including access token, expiry time and refresh token to get stored in localstorage.
}
);
return this.handle401Error(req, next);
// default: this.logoutUser();
}
} else {
return Observable.throw(error);
}
});
}
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.
// console.log('Bad Error');
return this.logoutUser();
}
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);
return this.authService.refreshToken()
.switchMap((newToken: string) => {
newToken = localStorage.getItem('access_token');
if (newToken) {
this.tokenSubject.next(newToken);
if (this.oldToken === newToken) {
return this.logoutUser();
} else {
return next.handle(this.addToken(req, newToken));
}
}
// 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)
this.router.navigate(['login']).then(() => { this.blockUI.stop(); });
return Observable.throw('');
}
}
现在 AuthService 用于获取刷新令牌和登录,每当需要刷新令牌时都会调用此服务,我给了 2 秒的时间间隔来获取刷新令牌
export class AuthService {
private TokenApi = AppSettings.DEVELOPMENT_API;
private newToken = ' ';
private current_token: string;
private refresh_token: string = localStorage.getItem('refresh_token');
constructor(private http: HttpClient, private localStorageToken: AppLocalStorage) {
}
login(username: string, password: string): Observable<TokenParams> {
const headers = new HttpHeaders({'content-type': 'application/x-www-form-urlencoded'});
const loginApi = this.TokenApi + '/oauth/token?username=' + username + '&password=' + password + '&grant_' +
'type=password....';
return this.http.post<TokenParams>(loginApi, '', {headers: headers});
}
refresh(): Observable<TokenParams> {
this.refresh_token = localStorage.getItem('refresh_token');
const refreshToken = this.TokenApi + '/oauth/token?refresh_token=' + this.refresh_token + '&grant_' +
'type=refresh_token...';
return this.http.post<TokenParams>(refreshToken, '' );
}
logout() {
this.localStorageToken.emptyLocalStorage();
}
getAuthToken() {
this.current_token = localStorage.getItem('access_token');
return this.current_token;
}
refreshToken(): Observable<string> {
this.newToken = localStorage.getItem('access_token');
this.current_token = this.newToken;
return Observable.of(localStorage.getItem('access_token')).delay(2000);
}
}
推荐阅读
- maven - 为什么 IntelliJ 不将 maven 依赖项显示为外部依赖项,并且即使下载了依赖项也显示红色符号
- delphi - 通用 TFieldGetTextEvent
- javascript - GIF 作为 ReactJS 中的背景
- java - Java- 从列表中读取第一个值并使用 Intellij 存储在变量中
- angular - 对 Angular / Ionic 项目执行增量部署?
- ios - expo-location 在 ios 中不起作用,并且未在应用设置中显示位置权限
- node.js - 使用 appstore connect api 和 node express app 下载 mobileprovision 配置文件
- typescript - 在切换 mat-slide-toggle 方法调用在 Angular 8 中不起作用
- bash - 您如何根据目录名称命名您的 docker 镜像?
- firebase - Firebase 身份验证:在注册时删除自动登录