網站首頁 編程語言 正文
對 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 刷新流程圖

前端方案:請求攔截
由于前端請求都是異步的,只有一個請求的時候,刷新 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
相關推薦
- 2022-10-22 python?中的?super詳解_python
- 2022-01-08 使用grid布局解決flex布局最后一行不足的問題
- 2022-12-14 C++利用類實現矩陣的數乘,乘法以及點乘_C 語言
- 2023-02-15 python中的lambda函數用法指南_python
- 2021-11-27 Linux開機自啟動服務兩種方式介紹_Linux
- 2022-10-05 nginx配置指令之server_name的具體使用_nginx
- 2022-04-12 React工作流程及Error?Boundaries實現過程講解_React
- 2023-05-14 Python實現批量導入1000條xlsx數據_python
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支