Angular 2:如何在全局范围内处理http oauth authtoken 401错误
我想使用全局错误处理程序处理访问令牌,并使用刷新令牌刷新它们。我的解决方案,几乎似乎工作如下:Angular 2:如何在全局范围内处理http oauth authtoken 401错误
ROM您的组件,订阅可观察到如下:
this.myHttpService.getSomeData().subscribe(response => {
console.log('user logged in', response);
},
error => {
// optionally do something custom here.
console.log(error);
throw error;
// we could handle the error here, getting the new auth token and calling this again.
}
上面的错误处理程序是没有必要的,因为我们可以在全球处理错误错误处理程序,但您可能需要在此处理其他事情。
在我的http服务,我有一个函数返回一个可观察到的是这样的:1。 捕捉错误 2.认识到:
getSomeData(): Observable<any> {
return this.http.get(this.API_URL_BASE + '/api/activities/getsomeData, this.httpHelperMethodsService.defaultOptions).map((response: Response) => {
return response.json();
}).catch(this.handleError);
}
来处理身份验证令牌到期将是如下过程身份验证令牌已过期 3.调用来获得一个新的身份验证令牌使用刷新令牌 4.重新运行源可观察
在服务上,实现处理错误的功能,它看起来像这样:
private handleError(error: any, originalObservable: any) {
let errorHelper = { 'error': error, 'observable': originalObservable};
return Observable.throw(errorHelper);
}
然后,您可以创建一个全局错误处理程序如下:
@Injectable()
export class ApplicationErrorHandler extends ErrorHandler {
constructor(private injector: Injector) {
super(false);
}
handleError(error: any): void {
const refreshTokenService = this.injector.get(RefreshTokenService);
...
else if(error.statusText == "Unauthorized"){
// check if token is expired. This will involve looking in local storage and comparing expiry time to current time.
if(isTokenExpired){
refreshTokenService.refreshToken(refreshToken).subscribe(response => {
// set refresh token
error.observable().subscribe(response => {
// return response.
}, error => {
// return error.
})
}
}
}
}
}
使用HTTP拦截,其配置如下:
import {Injectable} from "@angular/core";
import { ConnectionBackend, RequestOptions, Request, RequestOptionsArgs, Response, Http, Headers} from "@angular/http";
import {Observable} from "rxjs/Rx";
@Injectable()
export class InterceptedHttp extends Http {
constructor(backend: ConnectionBackend, defaultOptions: RequestOptions) {
super(backend, defaultOptions);
}
request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
return super.request(url, options);
}
get(url: string, options?: RequestOptionsArgs): Observable<Response> {
return super.get(url, this.getRequestOptionArgs(options));
}
post(url: string, body: string, options?: RequestOptionsArgs): Observable<Response> {
return super.post(url, body, this.getRequestOptionArgs(options));
}
put(url: string, body: string, options?: RequestOptionsArgs): Observable<Response> {
return super.put(url, body, this.getRequestOptionArgs(options));
}
delete(url: string, options?: RequestOptionsArgs): Observable<Response> {
return super.delete(url, this.getRequestOptionArgs(options));
}
private getRequestOptionArgs(options?: RequestOptionsArgs) : RequestOptionsArgs {
return this.getOptionsWithAccessToken();
}
getOptionsWithAccessToken() {
let accessToken = localStorage.getItem('access_token');
let accessTokenJson = <any>JSON.parse(accessToken);
let headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('Accept', 'application/json');
headers.append('Authorization', accessTokenJson);
let options = new RequestOptions({ headers: headers });
console.log('options updated', options);
return options;
}
}
的HTTP但是,当再次订阅原始的observable时,不会调用拦截器,而只是获得另一个401.我可以根据日志语句console.log('options updated', options);
这个拦截器在所有其他http调用之前被调用。看起来,http拦截器在你订阅原始观察器时不运行,是这种情况吗?
有没有办法更新此observable上的选项以使用新的身份验证令牌?也许我可以在observable上调用其他方法之一而不是订阅?或者这是不可能的?我能想到的唯一选择就是在每个http调用中捕获错误,但我有大约一百个,所以宁可不要这样做。
我使用Http
升级到HttpClient
。然后,我实现了新的httpInterceptor,在这里可以传入Injector
来访问其他服务,并检查每个请求以查看令牌是否即将到期,如果是这种情况则刷新它,否则捕获错误。
@Injectable()
export class MyInterceptor implements HttpInterceptor {
API_URL_BASE;
isCurrentlyRefreshing: boolean = false;
constructor(private injector: Injector) {
this.API_URL_BASE = myGlobals.API_GLOBAL_VAR;
}
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
if (!this.isCurrentlyRefreshing) {
var isTokenRefreshRequired = this.checkIfRefreshRequired();
if (isTokenRefreshRequired) {
this.refreshToken().subscribe(response => {
this.updateDetailsAfterLogin(response);
const httpService = this.injector.get(HttpHelperMethodsService);
httpService.loginFromRefreshToken();
this.isCurrentlyRefreshing = false;
console.log('response', response);
}, error => {
this.isCurrentlyRefreshing = false;
});
}
}
return next.handle(req).do(evt => {
}, err => {
if (err instanceof HttpErrorResponse && err.status == 401) {
let toastr = this.injector.get(TOASTR_TOKEN);
this.refreshToken().subscribe(response => {
this.updateDetailsAfterLogin(response);
const httpService = this.injector.get(HttpHelperMethodsService);
httpService.loginFromRefreshToken();
toastr.info('User session refreshed following timeout. Please retry.');
}, error => {
if(error.error){
if(error.error.error == "invalid_grant"){
toastr.error("session timed out");
var userService = this.injector.get(UserService);
var router = this.injector.get(Router);
userService.logout();
router.navigate(['/login']);
}
}
})
}
});
}
虽然我发现有可能从这里重烧看跌或POST请求,通过重新发送req
刷新令牌后,反应没有链接到初始观测。我决定在这些罕见的边缘情况下显示一个toastr是可以的。
https://angular.io/guide/http#intercepting-all-requests-or-responses –
谢谢。是的,这听起来像是解决方案。如果授权令牌设置在拦截器中,则它将按预期设置。 我现在要回家了,明天再试。 – Sam
@JBNizet我已经更新了这个问题,以包括我使用http拦截器的尝试,但不幸的是,这并没有奏效。 – Sam