ng6中,在HTTP攔截器裏,異步請求數據,以後再返回攔截器繼續執行用戶請求的方法研究

1、問題背景:

      上面繞口的標題不知道你們看不看的懂。一般咱們用攔截器就是兩個目的,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

2、時間的斷定邏輯

     

            時間斷定的邏輯不難,我只要在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);
      }
    }
  }

 

3、攔截器裏注入一個異步請求

       這個是難處理的,由於當前攔截器急迫的須要你返回一個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

相關文章
相關標籤/搜索