Angular 4 和 OAuth - 拦截 401 响应,刷新访问令牌并重试请求
Angular 4 and OAuth - Intercept 401 responses, refresh the access token and retry request
正如标题所说,我正在开发一个具有 OAuth 身份验证的 Angular 4 项目。
每当 HTTP 请求以状态代码 401 响应时,我都会拦截请求、更新访问令牌并重试失败的请求。
当我收到 401 时,请求被正确拦截,并且访问令牌得到了应有的刷新。失败的请求也会再次执行,但不再将其响应传递给组件。
所以问题是我的组件,它应该观察请求响应,在令牌刷新和请求重试发生之前向日志 'Error receiving the properties' 抱怨视图。
我的拦截器:
import { Injectable, Inject, Injector } from '@angular/core';
import {
HttpRequest,
HttpHandler,
HttpResponse,
HttpErrorResponse,
HttpEvent,
HttpInterceptor,
HttpSentEvent,
HttpHeaderResponse,
HttpProgressEvent,
HttpUserEvent
} from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import { AuthService } from './auth.service';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { TokenManager } from '../../../util/TokenManager';
import { AuthUserResponse } from '../../../models/authUserResponse';
import 'rxjs/add/operator/switchMap';
@Injectable()
export class AuthTokenExpiredInterceptor implements HttpInterceptor {
isRefreshingToken: boolean = false;
tokenSubject: BehaviorSubject<string> = new BehaviorSubject<string>(null);
constructor( private injector: Injector, private tokenManager: TokenManager ) {}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpSentEvent | HttpHeaderResponse | HttpProgressEvent | HttpResponse<any> | HttpUserEvent<any>> {
return next.handle(this.addNewAccessTokenToHeaders(request, this.tokenManager.retrieveAccessToken()))
.do((event: HttpEvent<any>) => {
if (event instanceof HttpResponse) {
console.log('processing response', event);
}
return event;
},(err) => {
if (err instanceof HttpErrorResponse) {
if (err.status === 401) {
console.log('Access_token possibly expired, trying to retrieve a new one!')
return this.handle401Error(request, next);
} else if (err.status === 400) {
console.log('Refresh_token possibly expired, redirecting to login');
return this.handle400Error(err);
}
} else {
return Observable.throw(err);
}
});
}
handle401Error(request: HttpRequest<any>, next: HttpHandler) {
if (!this.isRefreshingToken) {
console.log('in if');
this.isRefreshingToken = true;
// Reset here so that the following requests wait until the token comes back from the refreshToken call.
this.tokenSubject.next(null);
console.log('About to call renewAccessToken');
return this.injector.get(AuthService).renewAccessToken().subscribe((response) => {
let newToken = response.access_token;
if (newToken) {
console.log('Got the new access_token!');
this.tokenSubject.next(newToken);
let requestToRetry = this.addNewAccessTokenToHeaders(request, newToken);
console.log('The retried request header: ' + requestToRetry.headers.get("Authorization"));
return next.handle(requestToRetry);
} else { // No token in response
this.injector.get(AuthService).logout();
}
},
(err) => {
this.injector.get(AuthService).logout();
return Observable.throw(err)
},
() => {
console.log('handle401Error done');
this.isRefreshingToken = false;
})
} else {
console.log('In else');
return this.tokenSubject
.filter(token => token != null)
.take(1)
.switchMap(token => {
return next.handle(this.addNewAccessTokenToHeaders(request, token));
});
}
}
handle400Error(error: HttpErrorResponse) {
if (error && error.status === 400 && error.error && error.error.error === 'invalid_grant') {
this.injector.get(AuthService).logout();
}
return Observable.throw(error);
}
addNewAccessTokenToHeaders(req: HttpRequest<any>, token: string): HttpRequest<any> {
console.log('Adding the access_token to the Authorization header');
return req.clone({ setHeaders: {
Authorization: 'Bearer ' + token
}})
}
}
我的服务returns一个Observable
我的组件:
ngOnInit(){
this.getProperties();
}
getProperties() {
this.propertyService.getProperties().subscribe(
result => {
this.properties = result;
console.log('Received response in Properties component: ' + JSON.stringify(result));
}, error => {
console.log('Error receiving the properties for the view')
},
() => { console.log('Received the properties, now they can be displayed in the view') })
}
你的拦截函数必须return总是一个 Observable>。你的代码有点"bizarro"。我看到的主要问题是您使用 "do" 来捕获错误。 "do" 不修改请求。
我是这样拦截的(希望代码能帮到你)
constructor(private inj: Injector) { }
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
//if the request has "Authorization" we return the request
if (req.headers.has('Authorization'))
return next.handle(req);
//I get here the AuthService
const auth = this.inj.get(AuthService);
//create the httpHeaders
const httpHeaders = new HttpHeaders()
.set('Content-Type', 'application/json; charset=utf-8')
.set('Authorization', '' + auth.SID) //<-- I use auth.SID
const authReq = req.clone({ headers: httpHeaders });
return next.handle(authReq).catch((err: any) => { //<--if error use a catch
if (err instanceof HttpErrorResponse) {
if (err.status === 401) {
//auth.recoverSID return a Observable<{value:new SID}>
//use switchMap to really return next.handle(authReq)
return auth.recoverSID().switchMap((value: IResponse) => {
let httpHeaders = new HttpHeaders()
.set('Content-Type', 'application/json; charset=utf-8')
.set('Authorization', '' + value.SID)
const authReq = req.clone({ headers: httpHeaders });
return next.handle(authReq);
})
};
}
//Other case throw an error
return Observable.throw(err);
});
}
正如标题所说,我正在开发一个具有 OAuth 身份验证的 Angular 4 项目。
每当 HTTP 请求以状态代码 401 响应时,我都会拦截请求、更新访问令牌并重试失败的请求。
当我收到 401 时,请求被正确拦截,并且访问令牌得到了应有的刷新。失败的请求也会再次执行,但不再将其响应传递给组件。
所以问题是我的组件,它应该观察请求响应,在令牌刷新和请求重试发生之前向日志 'Error receiving the properties' 抱怨视图。
我的拦截器:
import { Injectable, Inject, Injector } from '@angular/core';
import {
HttpRequest,
HttpHandler,
HttpResponse,
HttpErrorResponse,
HttpEvent,
HttpInterceptor,
HttpSentEvent,
HttpHeaderResponse,
HttpProgressEvent,
HttpUserEvent
} from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import { AuthService } from './auth.service';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { TokenManager } from '../../../util/TokenManager';
import { AuthUserResponse } from '../../../models/authUserResponse';
import 'rxjs/add/operator/switchMap';
@Injectable()
export class AuthTokenExpiredInterceptor implements HttpInterceptor {
isRefreshingToken: boolean = false;
tokenSubject: BehaviorSubject<string> = new BehaviorSubject<string>(null);
constructor( private injector: Injector, private tokenManager: TokenManager ) {}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpSentEvent | HttpHeaderResponse | HttpProgressEvent | HttpResponse<any> | HttpUserEvent<any>> {
return next.handle(this.addNewAccessTokenToHeaders(request, this.tokenManager.retrieveAccessToken()))
.do((event: HttpEvent<any>) => {
if (event instanceof HttpResponse) {
console.log('processing response', event);
}
return event;
},(err) => {
if (err instanceof HttpErrorResponse) {
if (err.status === 401) {
console.log('Access_token possibly expired, trying to retrieve a new one!')
return this.handle401Error(request, next);
} else if (err.status === 400) {
console.log('Refresh_token possibly expired, redirecting to login');
return this.handle400Error(err);
}
} else {
return Observable.throw(err);
}
});
}
handle401Error(request: HttpRequest<any>, next: HttpHandler) {
if (!this.isRefreshingToken) {
console.log('in if');
this.isRefreshingToken = true;
// Reset here so that the following requests wait until the token comes back from the refreshToken call.
this.tokenSubject.next(null);
console.log('About to call renewAccessToken');
return this.injector.get(AuthService).renewAccessToken().subscribe((response) => {
let newToken = response.access_token;
if (newToken) {
console.log('Got the new access_token!');
this.tokenSubject.next(newToken);
let requestToRetry = this.addNewAccessTokenToHeaders(request, newToken);
console.log('The retried request header: ' + requestToRetry.headers.get("Authorization"));
return next.handle(requestToRetry);
} else { // No token in response
this.injector.get(AuthService).logout();
}
},
(err) => {
this.injector.get(AuthService).logout();
return Observable.throw(err)
},
() => {
console.log('handle401Error done');
this.isRefreshingToken = false;
})
} else {
console.log('In else');
return this.tokenSubject
.filter(token => token != null)
.take(1)
.switchMap(token => {
return next.handle(this.addNewAccessTokenToHeaders(request, token));
});
}
}
handle400Error(error: HttpErrorResponse) {
if (error && error.status === 400 && error.error && error.error.error === 'invalid_grant') {
this.injector.get(AuthService).logout();
}
return Observable.throw(error);
}
addNewAccessTokenToHeaders(req: HttpRequest<any>, token: string): HttpRequest<any> {
console.log('Adding the access_token to the Authorization header');
return req.clone({ setHeaders: {
Authorization: 'Bearer ' + token
}})
}
}
我的服务returns一个Observable
我的组件:
ngOnInit(){
this.getProperties();
}
getProperties() {
this.propertyService.getProperties().subscribe(
result => {
this.properties = result;
console.log('Received response in Properties component: ' + JSON.stringify(result));
}, error => {
console.log('Error receiving the properties for the view')
},
() => { console.log('Received the properties, now they can be displayed in the view') })
}
你的拦截函数必须return总是一个 Observable
我是这样拦截的(希望代码能帮到你)
constructor(private inj: Injector) { }
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
//if the request has "Authorization" we return the request
if (req.headers.has('Authorization'))
return next.handle(req);
//I get here the AuthService
const auth = this.inj.get(AuthService);
//create the httpHeaders
const httpHeaders = new HttpHeaders()
.set('Content-Type', 'application/json; charset=utf-8')
.set('Authorization', '' + auth.SID) //<-- I use auth.SID
const authReq = req.clone({ headers: httpHeaders });
return next.handle(authReq).catch((err: any) => { //<--if error use a catch
if (err instanceof HttpErrorResponse) {
if (err.status === 401) {
//auth.recoverSID return a Observable<{value:new SID}>
//use switchMap to really return next.handle(authReq)
return auth.recoverSID().switchMap((value: IResponse) => {
let httpHeaders = new HttpHeaders()
.set('Content-Type', 'application/json; charset=utf-8')
.set('Authorization', '' + value.SID)
const authReq = req.clone({ headers: httpHeaders });
return next.handle(authReq);
})
};
}
//Other case throw an error
return Observable.throw(err);
});
}