上面绕口的标题不知道你们看不看的懂。一般咱们用拦截器就是两个目的,javascript
一、在请求头里统一添加请求头。java
二、对响应结果预先处理。jquery
我如今项目就是利用拦截器,在请求头里增长:'Authorization': this.storage.token 的请求头。ajax
// 最精简的一个拦截器 。一下子 会在这个代码基础上增长后续讨论的代码 intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { const request = req.clone({ setHeaders: { 'Authorization': this.storage.token, } }); return next.handle(request); }
如今问题升级一下: token有一个固定的失效时间,30分钟。这样用户在连续使用系统时,一旦登陆时间到30分钟,token就失效了,回到登陆页面,体验很很差。 那么如何监测用户是在“连续活动”的时候,且当前token超时后,系统能自动获取新token,而且在以后请求中使用该新token呢? 简化一下表述:如何在拦截里中,判断token失效了能自动请求新token,而且把新token赋予当前的拦截请求中去。 其实这个事情要解决2个问题:session
一、时间的断定逻辑: 判断当前时间与 用户的上次活动时间和获取token的时间, 决定是让用户重登陆,仍是个人程序自动更新一下token,让用户继续访问系统。异步
二、拦截器异步注入一个请求:如何在拦截器里,加入一个异步请求token的操做 。 async
时间断定的逻辑不难,我只要在localstorage里保存一下登陆时间 和用户最近一次发出过请求的时间 便可。 我保存一个时间对象:{"token":1534312524914,"active":1534312524914} 来记录时间。ide
export interface IStoredTime { token: number; active: number; } // 全局的存储服务 @Injectable({ providedIn: 'root' }) export class AuthStorageService { private _timeKey = 'ss_tokenTime'; get time(): IStoredTime { const str = localStorage.getItem(this._timeKey); return str ? JSON.parse(str) : null; } set time(v: IStoredTime) { localStorage.setItem(this._timeKey, JSON.stringify(v)); } }
当用户登陆时记录保存时间:post
// 登陆后,当即保存时间 const time = +new Date(); this.storage.time = { token: time, active: time };
如今在拦截器里增长时间断定的业务的代码,针对三种状况,分别处理一下就行了:this
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { let request = req; // 若是是请求login,reToken if (req.url === refreshTokenURL || req.url === loginUrl) { return next.handle(request); } // 判断时间 const time = this.storage.time; if (time) { const now = +new Date(); const interval = 30 * 60 * 1000; // 30分钟的token失效时间,也是用户不活动的最大时间 if (now - time.active >= interval) { // 此时用户已是不活动用户了,直接跳转登陆页面 } else if (now - time.token >= interval) { // 此时用户仍然是活动的,但要更新一下token } else { // 正常请求,更新活动时间,并继续拦截器的流程 this.storage.time = { ...time, active: now }; request = req.clone({ setHeaders: { 'Authorization': this.storage.token, } }); return next.handle(request); } } }
这个是难处理的,由于当前拦截器急迫的须要你返回一个Observable对象,但你须要先异步走,请求到新token后, 把新token应用回当前拦截器。 异步请求token也会走拦截器。
思路一: 同步http请求新token。 我翻了ng的HttpClient文档,没找到同步的参数,像jquery.ajax 传入 {async:false} 这种。若是ng中有同步请求的方法,我认为它是可行的。若是有人知道同步怎么写,能够在下面留言。
思路二:委托一个新的Observable对象,接力实现。
一、既然当前拦截器须要返回一个Observable对象,我就先new一个Subject给拦截器,让它先返回一个Subject.
二、此时我就放心去异步请求新token,请求后,将新token赋于拦截器的本身的业务请求上。
三、当业务请求返回结果后,再触发第一步的Subject对象的next的方法。
此过程对用户无感的,默默地更新了token,他/她又能够愉快的玩耍30分钟了。
思路二的代码以下:
jumpLogin(msg) { this.router.navigate(['/login']); } // 从新获取token ,这里用了await来装13,其实能够用正常的subscribe()回调获取新token数据 async reTokenAsync(req: HttpRequest<any>, next: HttpHandler, sub: Subject<any>) { const reTokendData = await this.hc.post<any>(refreshTokenURL, { oldToken: this.storage.token }).toPromise(); const now = +new Date(); this.storage.time = { token: now, active: now }; // 更新时间 和 新token this.storage.token = reTokendData.refreshToken; const request = req.clone({ setHeaders: { 'Authorization': this.storage.token, } }); // 让真的业务请求重现江湖 next.handle(request).pipe( tap(data => { sub.next(data); // 数据到达,转达下发 return data; }, (error) => { sub.error(error); //数据报错,转达出错 } ) ).subscribe(); //因为该Observable对象已经没有人去主动订阅它了。因此咱们手动订阅一下,极重要!!!! } intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { let request = req; // 若是是请求login,reToken if (req.url === refreshTokenURL || req.url === loginUrl) { return next.handle(request); } // 判断时间 const time = this.storage.time; const subject = new Subject<any>(); // 被委托的对象 if (time) { const now = +new Date(); const interval = 30 * 60 * 1000; // 30分钟的token失效时间,也是用户不活动的最大时间 if (now - time.active >= interval) { // 不活动用户了,直接跳转 this.jumpLogin(); // 也返回个对象 。但它不会有地方触它 return subject; } else if (now - time.token >= interval) { // 活动的,需更新一下token this.reTokenAsync(req, next, subject); // 返回被委托的对象 。让真正的业务请求隐匿起来。 return subject; } else { // 正常请求,更新活动时间,并继续拦截器的流程 this.storage.time = { ...time, active: now }; request = req.clone({ setHeaders: { 'Authorization': this.storage.token, } }); return next.handle(request); } } }
思路二的核心有二:
一是在拦截器里建立一个 new Subject<any>(); 而后返回它。
其次是在从新获取token后,让原业务请求从新发生,并用要subscribe()一下。
=========================================================
这个问题解决的有点绕的,网上也搜索不到相关的技术文章。
这个问题最根本的缘由是不要设计token这种验证的机制,应该用session来作。
不过我也趁此机会,探索一下拦截器中的异步请求问题,在其它时候没准用的着吧
个人博客即将搬运同步至腾讯云+社区,邀请你们一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=1pgwko43rna2v