我正在尝试在收到角度为7的错误401时自动执行刷新令牌请求。
在我没有找到太多有关如何使用angular 7的文档以及我以前没有关于angular或rxjs的知识之间,我变得有些疯狂了
我认为它已经快要完成了,但是由于某种原因,第二个next.handle(newReq)不会发送请求(在Google chrome网络调试器中,仅会发出第一个请求)
我开始刷新并正确处理processLoginResponse(res)
您可以在这里看到我的拦截器
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
let newReq = req.clone();
return next.handle(req).pipe(
catchError(error => {
if (error.status == 401) {
this._authenticationService.refresh().subscribe(
res => {
this._authenticationService.processLoginResponse(res);
newReq.headers.set("Authorization", "Bearer " + this._authenticationService.authResponse.token)
return next.handle(newReq)
},
error => {
this._authenticationService.logOut();
});
}
throw error;
})
);
答案 0 :(得分:2)
您必须在所有请求中区分开。例如,您不想拦截您的登录请求,也不想拦截刷新令牌请求。 SwitchMap是您最好的朋友,因为您需要取消一些呼叫以等待令牌刷新。
因此,您要做的是先检查状态为401(未经授权)的错误响应:
return next.handle(this.addToken(req, this.userService.getAccessToken()))
.pipe(catchError(err => {
if (err instanceof HttpErrorResponse) {
// token is expired refresh and try again
if (err.status === 401) {
return this.handleUnauthorized(req, next);
}
// default error handler
return this.handleError(err);
} else {
return observableThrowError(err);
}
}));
在handleUnauthorized函数中,您必须刷新令牌并同时跳过所有其他请求:
handleUnauthorized (req: HttpRequest<any>, next: HttpHandler): Observable<any> {
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);
// get a new token via userService.refreshToken
return this.userService.refreshToken()
.pipe(switchMap((newToken: string) => {
// did we get a new token retry previous request
if (newToken) {
this.tokenSubject.next(newToken);
return next.handle(this.addToken(req, newToken));
}
// If we don't get a new token, we are in trouble so logout.
this.userService.doLogout();
return observableThrowError('');
})
, catchError(error => {
// If there is an exception calling 'refreshToken', bad news so logout.
this.userService.doLogout();
return observableThrowError('');
})
, finalize(() => {
this.isRefreshingToken = false;
})
);
} else {
return this.tokenSubject
.pipe(
filter(token => token != null)
, take(1)
, switchMap(token => {
return next.handle(this.addToken(req, token));
})
);
}
}
我们在拦截器类上有一个属性,用于检查是否已经有刷新令牌请求正在运行:this.isRefreshingToken = true;
,因为当您触发多个未经授权的请求时,您不希望有多个刷新请求。
if (!this.isRefreshingToken)
部分的所有内容都涉及刷新令牌并再次尝试上一个请求。
else
中处理的所有内容都是针对所有请求的,与此同时,当userService刷新令牌时,将返回tokenSubject,并且当令牌准备好this.tokenSubject.next(newToken);
时,每个跳过的请求都会被处理。重试。
此处是拦截器的起源灵感:https://www.intertech.com/Blog/angular-4-tutorial-handling-refresh-token-with-new-httpinterceptor/
答案 1 :(得分:0)
您可以执行以下操作:
import { HttpErrorResponse } from '@angular/common/http';
return next.handle(req).pipe(
catchError((err: any) => {
if (err instanceof HttpErrorResponse && err.status 401) {
return this._authenticationService.refresh()
.pipe(tap(
(success) => {},
(err) => {
this._authenticationService.logOut();
throw error;
}
).mergeMap((res) => {
this._authenticationService.processLoginResponse(res);
newReq.headers.set("Authorization", "Bearer " + this._authenticationService.authResponse.token)
return next.handle(newReq)
});
} else {
return Observable.of({});
}
}
));
答案 2 :(得分:0)
以下是用于调用刷新令牌的代码,并且在获取刷新令牌后调用了失败的API,
源代码中的注释将帮助您理解流程。 经过测试,适用于以下情况
1)如果单个请求由于401而失败,则将要求刷新 令牌,并将使用更新的令牌调用失败的API。
2)如果由于401而导致多个请求失败,则将要求刷新 令牌,并将使用更新的令牌调用失败的API。
3)它不会重复调用令牌API
如果仍然有人发现该代码无法正常工作的新情况,请通知我,以便我进行相应的测试和更新。
import { Injectable } from "@angular/core";
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor, HttpErrorResponse, HttpHeaders, HttpClient, HttpResponse } from "@angular/common/http";
import { Observable } from "rxjs/Observable";
import { throwError, BehaviorSubject } from 'rxjs';
import { catchError, switchMap, tap, filter, take, finalize } from 'rxjs/operators';
import { TOKENAPIURL } from 'src/environments/environment';
import { SessionService } from '../services/session.service';
import { AuthService } from '../services/auth.service';
/**
* @author Pravin P Patil
* @version 1.0
* @description Interceptor for handling requests which giving 401 unauthorized and will call for
* refresh token and if token received successfully it will call failed (401) api again for maintaining the application momentum
*/
@Injectable()
export class RefreshTokenInterceptor implements HttpInterceptor {
private isRefreshing = false;
private refreshTokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>(null);
constructor(private http: HttpClient, private sessionService: SessionService, private authService: AuthService) { }
/**
*
* @param request HttpRequest
* @param next HttpHandler
* @description intercept method which calls every time before sending requst to server
*/
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// Taking an access token
const accessToken = sessionStorage.getItem('ACCESS_TOKEN');
// cloing a request and adding Authorization header with token
request = this.addToken(request, accessToken);
// sending request to server and checking for error with status 401 unauthorized
return next.handle(request).pipe(
catchError(error => {
if (error instanceof HttpErrorResponse && error.status === 401) {
// calling refresh token api and if got success extracting token from response and calling failed api due to 401
return this.handle401Error(request, next);
} // If api not throwing 401 but gives an error throwing error
else {
return throwError(error);
}
}));
}
/**
*
* @param request HttpRequest<any>
* @param token token to in Authorization header
*/
private addToken(request: HttpRequest<any>, token: string) {
return request.clone({
setHeaders: { 'Authorization': `Bearer ${token}` }
});
}
/**
* This method will called when any api fails due to 401 and calsl for refresh token
*/
private handle401Error(request: HttpRequest<any>, next: HttpHandler) {
// If Refresh token api is not already in progress
if (this.isRefreshing) {
// 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(token => token != null),
take(1),
switchMap(jwt => {
return next.handle(this.addToken(request, jwt))
}));
} else {
// updating variable with api is in progress
this.isRefreshing = true;
// Set the refreshTokenSubject to null so that subsequent API calls will wait until the new token has been retrieved
this.refreshTokenSubject.next(null);
const refreshToken = sessionStorage.getItem('REFRESH_TOKEN');
// Token String for Refresh token OWIN Authentication
const tokenDataString = `refresh_token=${refreshToken}&grant_type=refresh_token`;
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/x-www-form-urlencoded',
'X-Skip-Interceptor': ''
})
};
return this.http.post<any>(TOKENAPIURL, tokenDataString, httpOptions)
.pipe(switchMap((tokens) => {
this.isRefreshing = false;
this.refreshTokenSubject.next(tokens.access_token);
// updating value of expires in variable
sessionStorage.setItem('ACCESS_TOKEN', tokens.access_token);
return next.handle(this.addToken(request, tokens.access_token));
}));
}
}
}