Angular 7 - 如何共享令牌更新 Observable?
Angular 7 - How can I share the token renewal Observable?
在每个请求之前,HTTP 拦截器会检查访问令牌是否过期,如果是,它会首先更新令牌,然后继续请求。
问题出在有多个请求的页面上。在这些页面上,拦截器尝试为每个请求更新令牌。
在这种情况下,如何共享令牌续订请求?
负责更新令牌的方法是这样的:
renewToken() {
let refreshToken = // get refresh token
let accessToken = // get access token
if (!refreshToken || !accessToken)
return;
let requestData = {
accessToken: accessToken,
refreshToken: refreshToken
}
return this.http.post<ApplicationToken>(`${this.baseUrl}/renewToken`, requestData)
.pipe(
// I tried share() shareReplay(1) and publishReplay(1) here but no luck
);
}
这就是拦截器使用该方法的方式:
...
// in case we need to renew the token
return this.accountService.renewToken()
.pipe(
switchMap(t => {
this.accountService.saveToken(t);
token = this.accountService.getAccessToken();
var newReq = this.setToken(req, token);
return next.handle(newReq);
})
);
...
您需要检查刷新令牌请求是否正在进行,因为您不希望其他调用进来并再次调用 refreshToken。
我在这里为您创建了 RefreshTokenInterceptor class。
你只需要定制,关注评论即可:
import { Injectable } from "@angular/core";
import { HttpInterceptor, HttpEvent, HttpRequest, HttpHandler } from "@angular/common/http";
import { BehaviorSubject, Observable } from "rxjs";
import { catchError, switchMap, filter, take } from "rxjs/operators";
@Injectable({
providedIn: 'root'
})
export class RefreshTokenInterceptor implements HttpInterceptor {
private refreshTokenInProgress: boolean = false;
private refreshTokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>(
null
);
constructor(public accountService: AccountService) {}
intercept(request: HttpRequest<any>,next: HttpHandler): Observable<HttpEvent<any>> {
// Check first if token has expired
// If not, just add addAuthenticationToken();
// If expired
if (tokenHasExpired()) {
if (this.refreshTokenInProgress) {
// If refreshTokenInProgress is true, we will wait until refreshTokenSubject has a non-null value
// – which means the new token is ready and we can retry the request again
return this.refreshTokenSubject.pipe(
filter(result => result !== null),
take(1),
switchMap(() => next.handle(this.addAuthenticationToken(request)))
);
} else {
this.refreshTokenInProgress = true;
// Set the refreshTokenSubject to null so that subsequent API calls will wait until the new token has been retrieved
this.refreshTokenSubject.next(null);
return this.accountService.renewToken()
.pipe(
switchMap(t => {
this.accountService.saveToken(t);
let token = this.accountService.getAccessToken();
this.refreshTokenInProgress = false; // Set refreshTokenInProgress to False
this.refreshTokenSubject.next(token); // Add token to the refreshTokenSubject
var newReq = this.setToken(req, token);
return next.handle(newReq);
}),
catchError((err) => {
this.refreshTokenInProgress = false;
return Observable.throw(err);
})
);
}
} else {
return this.addAuthenticationToken(request);
}
}
addAuthenticationToken(request) {
// Get access token from Local Storage
const accessToken = this.accountService.getAccessToken();
// If access token is null this means that user is not logged in
// And we return the original request
if (!accessToken) {
return request;
}
// We clone the request, because the original request is immutable
return request.clone({
setHeaders: {
Authorization: accessToken
}
});
}
}
在每个请求之前,HTTP 拦截器会检查访问令牌是否过期,如果是,它会首先更新令牌,然后继续请求。
问题出在有多个请求的页面上。在这些页面上,拦截器尝试为每个请求更新令牌。
在这种情况下,如何共享令牌续订请求?
负责更新令牌的方法是这样的:
renewToken() {
let refreshToken = // get refresh token
let accessToken = // get access token
if (!refreshToken || !accessToken)
return;
let requestData = {
accessToken: accessToken,
refreshToken: refreshToken
}
return this.http.post<ApplicationToken>(`${this.baseUrl}/renewToken`, requestData)
.pipe(
// I tried share() shareReplay(1) and publishReplay(1) here but no luck
);
}
这就是拦截器使用该方法的方式:
...
// in case we need to renew the token
return this.accountService.renewToken()
.pipe(
switchMap(t => {
this.accountService.saveToken(t);
token = this.accountService.getAccessToken();
var newReq = this.setToken(req, token);
return next.handle(newReq);
})
);
...
您需要检查刷新令牌请求是否正在进行,因为您不希望其他调用进来并再次调用 refreshToken。
我在这里为您创建了 RefreshTokenInterceptor class。
你只需要定制,关注评论即可:
import { Injectable } from "@angular/core";
import { HttpInterceptor, HttpEvent, HttpRequest, HttpHandler } from "@angular/common/http";
import { BehaviorSubject, Observable } from "rxjs";
import { catchError, switchMap, filter, take } from "rxjs/operators";
@Injectable({
providedIn: 'root'
})
export class RefreshTokenInterceptor implements HttpInterceptor {
private refreshTokenInProgress: boolean = false;
private refreshTokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>(
null
);
constructor(public accountService: AccountService) {}
intercept(request: HttpRequest<any>,next: HttpHandler): Observable<HttpEvent<any>> {
// Check first if token has expired
// If not, just add addAuthenticationToken();
// If expired
if (tokenHasExpired()) {
if (this.refreshTokenInProgress) {
// If refreshTokenInProgress is true, we will wait until refreshTokenSubject has a non-null value
// – which means the new token is ready and we can retry the request again
return this.refreshTokenSubject.pipe(
filter(result => result !== null),
take(1),
switchMap(() => next.handle(this.addAuthenticationToken(request)))
);
} else {
this.refreshTokenInProgress = true;
// Set the refreshTokenSubject to null so that subsequent API calls will wait until the new token has been retrieved
this.refreshTokenSubject.next(null);
return this.accountService.renewToken()
.pipe(
switchMap(t => {
this.accountService.saveToken(t);
let token = this.accountService.getAccessToken();
this.refreshTokenInProgress = false; // Set refreshTokenInProgress to False
this.refreshTokenSubject.next(token); // Add token to the refreshTokenSubject
var newReq = this.setToken(req, token);
return next.handle(newReq);
}),
catchError((err) => {
this.refreshTokenInProgress = false;
return Observable.throw(err);
})
);
}
} else {
return this.addAuthenticationToken(request);
}
}
addAuthenticationToken(request) {
// Get access token from Local Storage
const accessToken = this.accountService.getAccessToken();
// If access token is null this means that user is not logged in
// And we return the original request
if (!accessToken) {
return request;
}
// We clone the request, because the original request is immutable
return request.clone({
setHeaders: {
Authorization: accessToken
}
});
}
}