日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

axios token失效刷新token怎么重新請求_Token 刷新并發處理解決方案

作者:Lance-king 更新時間: 2022-04-17 編程語言

對 Token 進行刷新續期,我們要解決并發請求導致重復刷新 Token 的問題,這也是設計刷新 Token 的難點。這里我會分別介紹前端和后端各自的處理方案。

后端方案:利用 Redis 緩存

當同時發起多個請求時,第一個接口刷新了 Token,后面的請求仍然能通過請求,且不造成 Token 重復刷新。那么,后端在用戶第一次登錄時,需要將生成的 Token 數據(token 和 createTime)緩存一份到 Redis 中。

當 Token 過期時,重新生成新的 Token 數據并更新 Redis 緩存,同時在 Redis 中設置一條 Token 過渡數據并設置一個很短的過期時間(比如 30s)。如果后面的請求發現 Token 已經被刷新了,就判斷 Redis 中是否存在 Token 過渡數據,存在就放行,這樣同一時間的請求都可以通過。

源碼地址:https://github.com/yifanzheng/spring-security-jwt/tree/refresh-token-redis

Token 刷新流程圖

45d9b73f661920f46f746493ace774c5.png

前端方案:請求攔截

由于前端請求都是異步的,只有一個請求的時候,刷新 Token 是比較好處理的,但并發請求下刷新 Token 處理起來有點麻煩。我們需要考慮在多個請求幾乎同時發起并且 Token 都失效的情況,當第一個請求進入 Token 刷新流程時,其他請求必須等待第一個請求完成 Token 刷新后再使用新 Token 進行重試。
簡單地講,就是同一時間有多個請求且 Token 都失效,在第一個請求進行 Token 刷新時,其他請求必須處于等待狀態,直到 Token 刷新完成,才能攜帶新 Token 進行重試。

下面,我使用了 Angular 的請求攔截器,利用 BehaviorSubject 進行 Token 刷新狀態的監聽,當 Token 刷新成功,放行后面的請求進行重試。

除此之外,前端還可以利用 Promise,將請求存進隊列中后,同時返回一個 Promise,讓這個 Promise 一直處于 Pending 狀態(即不調用 resolve),此時這個請求就會一直等待,只要我們不執行 resolve,這個請求就會一直在等待。當刷新 Token 的請求完成后 ,我們再調用 resolve,逐個重試。

Github 地址:https://github.com/yifanzheng/spring-security-jwt/tree/refresh-token-frontend

Angular 代碼示列

import { Injectable } from "@angular/core";
import {
  HttpEvent,
  HttpInterceptor,
  HttpHandler,
  HttpRequest,
  HttpErrorResponse
} from "@angular/common/http";
import { throwError, Observable, BehaviorSubject, of } from "rxjs";
import { catchError, filter, finalize, take, switchMap, mergeMap } from "rxjs/operators";

@Injectable()
export class AuthInterceptor implements HttpInterceptor {

  private refreshTokenInProgress = false;
  private refreshTokenSubject: BehaviorSubject = new BehaviorSubject(false);

  intercept(req: HttpRequest, next: HttpHandler): Observable> {
    if (!req.headers.has("Content-Type")) {
      req = req.clone({
        headers: req.headers.set("Content-Type", "application/json")
      });
    }
    // 統一加上服務端前綴
    let url = req.url;
    if (!url.startsWith('https://') && !url.startsWith('http://')) {
      url = "./" + url;
    }
    req = req.clone({ url });
    req = this.setAuthenticationToken(req);

    return next.handle(req).pipe(
      mergeMap((event: any) => {
        // 若一切都正常,則后續操作
        return of(event);
      }),
      catchError((error: HttpErrorResponse) => {
        // 當是 401 錯誤時,表示 Token 已經過期,需要進行 Token 刷新
        if (error && error.status === 401) {
          if (this.refreshTokenInProgress) {
            // 如果 refreshTokenInProgress 為 true,我們將等到 refreshTokenSubject 是 true 時,才可以再次重試該請求
            // 這表示刷新 Token 動作已完成,新 Token 已準備就緒
            return this.refreshTokenSubject.pipe(
              filter(result => result),
              take(1),
              switchMap(() => next.handle(this.setAuthenticationToken(req)))
            );
          } else {
            this.refreshTokenInProgress = true;
            // 將 refreshTokenSubject 設置為 false,以便后面的請求調用時將處于等待狀態,直到檢索到新 Token 為止
            this.refreshTokenSubject.next(false);
            return this.refreshToken().pipe(
              switchMap((newToken: string) => {
                this.refreshTokenSubject.next(true);
                // 重新設置新的 Token
                localStorage.setItem("token", newToken);
                return next.handle(this.setAuthenticationToken(req));
              }),
              // 當刷新 Token 請求完成后,需要將 refreshTokenInProgress 設置為 false,用于下次刷新 Token
              finalize(() => (this.refreshTokenInProgress = false))
            );
          }
        } else {
          return throwError(error);
        }
      })
    );
  }

  private refreshToken(): Observable {
    // 這里需要換成實際的 Token 刷新接口
    return of("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzdGFyIiwicm9sZSI6WyJST0xFX1VTRVIiXSwiaXNzIjoic2VjdXJpdHkiLCJpYXQiOjE2MDY4MjczMDAsImF1ZCI6InNlY3VyaXR5LWFsbCIsImV4cCI6MTYwNjgzNDUwMH0.Hiq2DsH6j4XFd_v87lDWGlYembTLck7DjMLRLWdyvOo");
  }

  private setAuthenticationToken(request: HttpRequest): HttpRequest {
    return request.clone({
      headers: request.headers.set("Authorization", "Bearer " + localStorage.getItem("token"))
    });
  }
}

原文鏈接:https://blog.csdn.net/weixin_34313368/article/details/112127419

欄目分類
最近更新