我正在构建一个需要身份验证的Angular应用程序。这是通过令牌完成的。令牌的使用寿命很短,需要定期刷新。我已经引用了这个question,但我觉得我的需求和代码结构是截然不同的(例如,我不是要“持有”这个类而且我有单独的拦截器)。
一个拦截器的主要目的是将标记添加到标题:
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(private authService: AuthService) {
}
intercept(req: HttpRequest<any>, next: HttpHandler):
Observable<HttpEvent<any>> {
const authToken = this.authService.getToken();
const refreshToken = this.authService.getRefreshToken();
// If refresh token is expired, the user will need to relogin
if(refreshToken == null || this.authService.checkTokenExpired(true)) {
this.authService.logout();
return next.handle(req);
}
// There is no token to send, continue.
if (authToken == null || this.authService.checkTokenExpired()) {
return next.handle(req);
}
const authReq = req.clone({ setHeaders: { Authorization: "Bearer " + authToken } });
return next.handle(authReq);
}
}
我有一个单独的拦截器来处理“401”错误:
@Injectable()
export class LoggingInterceptor implements HttpInterceptor {
constructor(private authService: AuthService) {
}
intercept(req: HttpRequest<any>, next: HttpHandler):
Observable<HttpEvent<any>> {
const cBack = tap(res => {
console.log("Refreshed");
console.log("Token is " + this.authService.getToken());
next.handle(req.clone
({ setHeaders: { Authorization: "Bearer " + this.authService.getToken() } }));
})
return next.handle(req)
.catch((err: HttpErrorResponse) => {
if (err.status == 401) {
this.authService.refresh()
.pipe(cBack).subscribe();
}
console.log(err);
return _throw(err.message);
});
};
}
AuthService如下:
@Injectable()
export class AuthService {
private authURL: string = "http://localhost:8090/oauth/token";
private loginPath : string = "/login";
isLoggedIn: boolean = false;
redirectURL: string;
constructor(private http: HttpClient) {
this.redirectURL = "";
this.isLoggedIn = this.getToken() != null;
}
login(user: string, password: string): Observable<boolean> {
var data = new FormData();
data.append("grant_type", "password");
data.append("username", user);
data.append("password ", password);
const httpOptions = {
headers: new HttpHeaders({
'Authorization': 'Basic ' + window.btoa("web:secret")
})
};
return this.http.post<AuthResponseModel>(this.authURL, data, httpOptions)
.pipe(
map((r: AuthResponseModel) => {
if (r.access_token) {
localStorage.setItem("access_token", r.access_token);
localStorage.setItem("refresh_token", r.refresh_token);
this.isLoggedIn = true;
return true;
}
}
));
};
refresh() : Observable<boolean> {
var data = new FormData();
data.append("grant_type", "refresh_token");
data.append("refresh_token", this.getRefreshToken());
const httpOptions = {
headers: new HttpHeaders({
'Authorization': 'Basic ' + window.btoa("web:secret")
})
};
return this.http.post<AuthResponseModel>(this.authURL, data, httpOptions)
.pipe(
map( r => {
localStorage.setItem("access_token", r.access_token);
localStorage.setItem("refresh_token", r.refresh_token);
this.isLoggedIn = true;
return true;
})
)
}
logout(): void {
localStorage.removeItem("access_token");
localStorage.removeItem("refresh_token");
this.isLoggedIn = false;
}
checkTokenExpired(checkRefresh = false) : boolean {
if(checkRefresh) { return decode(this.getRefreshToken()).exp < (Date.now().valueOf() / 1000); }
return decode(this.getToken()).exp < (Date.now().valueOf() / 1000);
}
getToken(): string {
return localStorage.getItem("access_token");
}
getRefreshToken() : string {
return localStorage.getItem('refresh_token');
}
}
当应用程序收到401时,它会正确发送刷新令牌的请求。我从日志拦截器获得“刷新”和“令牌是......”console.log
消息。此外,它确实将令牌放在本地存储中。如果我手动刷新页面,它实际上是有效的。但是,我的最终目标是,当应用程序收到401时,刷新令牌,然后重新发送原始请求。最后一步是当前无法正常运行的行为。
我将不胜感激任何帮助。感谢。
编辑:
版本,带有retryWhen。
let interceptor = this;
return next.handle(req.clone({ setHeaders: { Authorization: "Bearer " + this.authService.getToken() } }))
.pipe(
retryWhen(error$ => {
return error$.pipe(
mergeMap(error => {
if (error.status === 401) {
return interceptor.authService.refresh();
} else {
// re-throw
_throw(error)
}
})
);
})
);
答案 0 :(得分:1)
不确定为什么需要Auth和Logging拦截器,但我认为你可以这样做:
class Interceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next
.handle(req)
.pipe(
retryWhen(error$ => {
return error$.pipe(
mergeMap(error => {
if (error.status === 401) {
// fetch new token + retry
return this.someService.refreshToken();
} else {
// re-throw
_throw(error)
}
})
);
})
);
}