Angular 4 和 OAuth - 拦截 401 响应,刷新访问令牌并重试请求
Posted
技术标签:
【中文标题】Angular 4 和 OAuth - 拦截 401 响应,刷新访问令牌并重试请求【英文标题】:Angular 4 and OAuth - Intercept 401 responses, refresh the access token and retry request 【发布时间】:2018-05-05 04:48:13 【问题描述】:正如标题所说,我正在开发一个带有 OAuth 身份验证的 Angular 4 项目。
每当 http 请求响应状态码 401 时,我都会拦截该请求,更新访问令牌并重试失败的请求。
当我收到 401 时,请求被正确拦截,并且访问令牌会按应有的方式刷新。失败的请求也会再次执行,但不再向组件传递它的响应。
所以问题在于,我的组件应该在请求响应中观察到,在令牌刷新和请求重试发生之前,该组件会抱怨日志“接收属性时出错”。
我的拦截器:
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
)
我的服务返回一个 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') )
【问题讨论】:
【参考方案1】:你的函数拦截必须总是返回一个 Observable > >。你的代码有点“怪诞”。我看到的主要问题是您使用“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);
);
【讨论】:
请注意next.handle().catch
在 Angular 5 和最新的 RxJS 库中已更改:***.com/a/47841788/2988073
感谢广告,@closedloop(当我写答案时,Angular 仍然使用 Rxjs 5)现在我们必须使用 pipe(catchError(err=>...),参见 github.com/ReactiveX/rxjs/blob/master/docs_app/content/guide/v6/…以上是关于Angular 4 和 OAuth - 拦截 401 响应,刷新访问令牌并重试请求的主要内容,如果未能解决你的问题,请参考以下文章
Angular 4/5 + Spring Boot + Oauth2 支持
如何在Angular 4中提供用户名和密码以使用具有oAuth安全性的rest控制器?