在我的应用程序中,我正在使用Angular HttpInterceptor跟踪每个传出的HTTP请求,并将本地存储中存储的JWT令牌添加到请求标头中,以便后端API可以通过检查JWT令牌来授权请求。
为此,拦截器只需获取HTTP请求并通过设置标头添加令牌,然后调用next.handle(req)
来传播请求。
这是使用以下代码完成的:
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
req = this.addHeaders(req);
return next.handle(req).pipe(
catchError(error => {
if (error instanceof HttpErrorResponse && error.status === 401) {
this._authService.signOut("unauthorized");
}
if (error instanceof HttpErrorResponse && error.status === 403) {
this._authService.signOut("expired");
}
return throwError(error);
})
);
}
/**
* Add access_token to header.
* @param req
*/
addHeaders(req: HttpRequest<any>): HttpRequest<any> {
return req.clone({
setHeaders: {
"Content-type": "application/json",
token: this._authService.getAuthenticationToken(),
jwt_token: this._authService.getAuthorizationToken(),
[this._config.subscriptionKeyName]: this._config.subscriptionKey || ""
}
});
}
我想扩展此功能,以便拦截器首先检查以确保存储在浏览器本地存储中的JWT令牌有效。如果令牌无效,则拦截器本身应触发单独的HTTP请求,以从API端点检索新的JWT令牌,该令牌应用于覆盖旧令牌,然后再继续原始请求。
我试图弄清楚如何在Angular代码中构造此行为,但我一直遇到无限循环。
这是我目前的代码。
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
if (req.headers.has("newtokenrequest")) {
next.handle(req);
}
let jwtToken: string = this._authService.getLocalAuthorizationToken();
if (!this.checkJwtTokenIsValid(jwtToken)) { //Invalid token, get new one
const authTokenStream = this._authService.getApiAuthorizationToken$();
authTokenStream.subscribe(token => {
this._authService.setAuthorizationToken(token);
});
}
req = this.addHeaders(req);
return next.handle(req).pipe(
catchError(error => {
if (error instanceof HttpErrorResponse && error.status === 401) {
this._authService.signOut("unauthorized");
}
if (error instanceof HttpErrorResponse && error.status === 403) {
this._authService.signOut("expired");
}
return throwError(error);
})
);
}
这是返回Observable<string>
的代码,该代码检索新的JWT令牌。
getApiAuthorizationToken$(): Observable<string> {
let headers = new HttpHeaders();
headers = headers.set('newtokenrequest', 'true');
return this._httpClient.get<string>(this._configService.getConfig().teacherAuthAPI, {headers: headers});
}
代码下降到authTokenStream订阅,然后再次循环回到顶部。我认为这是因为订阅正在向后端发送新的HTTP请求以检索令牌,但是此请求应包括newtokenrequest
作为标头,应由应调用{{ 1}},允许请求继续进行而无需进一步的交互,但这不会发生,而是代码循环。
编辑:更新
next.handle(req)
答案 0 :(得分:0)
您应该做的是将检查本地存储有效性的工作分配给authguard。
canActivate(): Observable<boolean> | Promise<boolean> | boolean {
switch (this.ispectUrl(this.url)) {
// I'm checking if there is a jwt in url
case true: {
const token = this.refactorUrl(this.url);
this.auth.setAccessToken(token);
return this.auth.validateJwt();
break;
}
// No jwt in url? I check possibile jwt in localstorage
case false: {
return this.auth.validateJwt();
break;
}
// Neither of both, simply i logout to login page session expired
default: {
this.auth.logout();
return false;
}
}
}
这里我要从url中提取jwt,因为我使用的是CAS实现,但是如果url上没有任何jwt,我只需在执行操作之前检查本地存储,如果jwt有效,则用户可以浏览所有受authguard保护的网址,请记住,在检查jwt有效性之后,您应该提取jwt的到期日期,并从中知道何时检查刷新。
答案 1 :(得分:0)
通过使用HttpBackend
服务而不是HttpClient
,可以避免在请求授权令牌时出现循环。 HttpBackend
是HttpClient
使用的低级Http服务。它不考虑拦截器。
import {HttpBackend, HttpHeaders, HttpRequest, HttpResponse} from '@angular/common/http';
constructor(private httpBackend: HttpBackend) {
}
getApiAuthorizationToken$(): Observable<string> {
const headers = new HttpHeaders({
newtokenrequest: 'true'
});
const request = new HttpRequest<string>(
'GET',
this._configService.getConfig().teacherAuthAPI,
{
headers
});
return this.httpBackend.handle(request).pipe(
map((response: HttpResponse<string>) => response.body),
);
}
UPD。
替换
const authTokenStream = this._authService.getApiAuthorizationToken$();
authTokenStream.subscribe(token => {
this._authService.setAuthorizationToken(token);
return next.handle(req);
});
使用
return this.this._authService.getApiAuthorizationToken$().pipe(
tap(token => this._authService.setAuthorizationToken(token)),
switchMap(() => {
req = this.addHeaders(req);
return next.handle(req);
})
);
尝试避免在拦截器内部调用订阅。