<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【SpringBoot】 如何做到無感刷新token?

          共 31157字,需瀏覽 63分鐘

           ·

          2024-07-31 07:40

          關注我們,設為星標,每天7:40不見不散,架構路上與您共享

          回復架構師獲取資源


          大家好,我是你們的朋友架構君,一個會寫代碼吟詩的架構師。


          1. 前言

          最近在搞一個鑒權認證服務器,其中有個問題就是token的無感刷新。Token無感刷新是一種在用戶不感知的情況下自動更新訪問令牌(Token)的機制,以維持用戶的登錄狀態(tài)。

          一般是使用一個短期的token來做權限認證,而更長時間的refreshToken來做短token的刷新,而在實現(xiàn)的過程中就有各種問題出來比如:

          • Q1: 是要在服務器端實現(xiàn)還是能在客戶端實現(xiàn)?
          • Q2: token過期后無法解析,怎么獲取到其中的過期時間?
          • Q3: 無感刷新即是需要在獲取到新token后重發(fā)原來的request請求,并將二次請求的結果返回給原調用者,如何實現(xiàn)?

          下面我就對上面這些問題給出我自己的拙見,希望能對讀者有所幫助??

          2. 客戶端實現(xiàn)

          2.1 初始版本

          想法:每次客戶端發(fā)起的請求會被服務器端gateway攔截,此時在gateway中判斷token是否無效(過期):

          • 過期則返回一個特定的狀態(tài)碼(可以自定義也可以用HTTPStatus)告訴客戶端當前token失效
          • 沒過期則放行,繼續(xù)原本的業(yè)務邏輯

          而前端處可以攔截到當前服務器返回的響應狀態(tài)碼,根據狀態(tài)碼來執(zhí)行對應的操作,也就是下面要引出的axios

          2.1.1 服務器端gateway實現(xiàn)攔截器

          注意環(huán)境springboot3+java17,通過繼承GlobalFilter來實現(xiàn)對應的filter邏輯

          @Component
          public class MyAccessFilter implements GlobalFilterOrdered
          {
              @Override
              public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                  ServerHttpRequest request = exchange.getRequest();
                  String uri = request.getURI().getPath();
                  HttpMethod method = request.getMethod();

                  // OPTION直接放行
                  if(method.matches(HttpMethod.OPTIONS.name()))
                      return chain.filter(exchange);

                  //登錄請求直接放行
                  if(SecurityAccessConstant.REQUEST_LOGGING_URI.equals(uri) && method.matches(HttpMethod.POST.name()))
                      return chain.filter(exchange);
             
            //獲取token
                  String token = JWTHelper.getToken(request.getHeaders().getFirst(SecurityAccessConstant.HEADER_NAME_TOKEN));
                  if(null != token){

                      //判斷token是否過時
                      if(!JWTHelper.isOutDate(token)){
                          return chain.filter(exchange);

                      }else{
                          if(!SecurityAccessConstant.REQUEST_REFRESH.equals(uri))    //當前不是刷新請求可以刷新返回的狀態(tài)碼就是511
                              return ResponseUtils.out(exchange , ResultData.fail(ResultCodeEnum.NEED_TO_REFRESH_TOKEN.getCode(),
                                  ResultCodeEnum.NEED_TO_REFRESH_TOKEN.getMessage()));

                          //當前是刷新請求 但refreshToken都過期了,即刷新不支持
                          return ResponseUtils.out(exchange , ResultData.fail(ResultCodeEnum.RC401.getCode(), ResultCodeEnum.RC401.getMessage()));
                      }

                  }
                  
                  return ResponseUtils.out(exchange , ResultData.fail(ResultCodeEnum.RC401.getCode(), ResultCodeEnum.RC401.getMessage()));
              }

              @Override
              public int getOrder() {
                  //數(shù)值越小 優(yōu)先級越高
                  return Ordered.LOWEST_PRECEDENCE;
              }
          }
          2.1.1.1 問題Q2解決

          正常情況下解析的token會報錯,那么就在解析的時候攔截錯誤,如果catch 到JwtException,此時就認為該token無效已經過期了返回true

          否則則執(zhí)行正常邏輯獲取并返回token中的過期時間與當前時間比較的結果

          //判斷當前token是否過期
          public static boolean isOutDate(String token){
              try {
                  Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
                  Date expirationDate = claimsJws.getBody().getExpiration();
                  return expirationDate.before(new Date());
              } catch (JwtException e) {
                  // JWT token無效或已損壞
                  return true;
              }
          }

          2.1.2 axios攔截器

          在攔截器中,我們使用判斷響應碼,如果是401則清空用戶數(shù)據回退到登錄頁面,而如果是511則使用refreshToken再請求刷新一次(其他的情況在這里就不做分析,感興趣的讀者可以自行研究)

          // 響應攔截器
          service.interceptors.response.use(
           
            // 響應成功進入第1個函數(shù)
            // 該函數(shù)的參數(shù)是響應對象
            function(response) {
              console.log(response)
              return response.data.data;
            },
            // 響應失敗進入第2個函數(shù),該函數(shù)的參數(shù)是錯誤對象
            async function(error) {
              // 如果響應碼是 401 ,則請求獲取新的 token
              // 響應攔截器中的 error 就是那個響應的錯誤對象
              if(error.response == undefined)
                return Promise.reject(error);

              const status = error.response.status
              const authStore = useAuthStore()
              let message = ''

              switch(status){
                case 401// 無權限
                  authStore.reset() // 清空store中的權限數(shù)據
                  window.sessionStorage.removeItem('isAuthenticated')
                  window.sessionStorage.removeItem('token')
                  window.sessionStorage.removeItem('refreshToken')
                  message = 'token 失效,請重新登錄'
                  // 跳轉到登錄頁  
                  window.location.href = '/auth/login';
                  break;
                case 511// 當前token需要刷新
                  try {
                    const data = refresh()

                    if(data !== null){
                      data.then((value) => {
                        // Use the string value here
                        if(value !== ''){
                          // 如果獲取成功,則把新的 token 更新到容器中
                          console.log("刷新 token  成功", value);
                          window.sessionStorage.setItem("token",value)
                          // 把之前失敗的用戶請求繼續(xù)發(fā)出去
                          // config 是一個對象,其中包含本次失敗請求相關的那些配置信息,例如 url、method 都有
                          // return 把 request 的請求結果繼續(xù)返回給發(fā)請求的具體位置
                          error.config.headers['Authorization'] = 'Bearer ' +value;
                          return service(error.config);
                        }
                        console.log(value);
                      }).catch((error) => {
                        // Handle any errors that occurred while resolving the promise
                        console.error(error);
                      });
                      
                    }
                    
                  } catch (err) {
                    // 如果獲取失敗,直接跳轉 登錄頁
                    console.log("請求刷線 token 失敗", err);
                    router.push("/login");
                  }
                  break;
                  case '403':
                    message = '拒絕訪問'
                    break;
                  case '404':
                    message = '請求地址錯誤'
                    break;
                  case '500':
                    message = '服務器故障'
                    break;
                  default:
                    message = '網絡連接故障'  
             
              }
              
              Message.error(message)
              return Promise.reject(error);
            }
          );

          2.1.3 refresh刷新token方法實現(xiàn)

          這里實現(xiàn)是重新用axios原生發(fā)異步請求,而不是使用在request.ts 中導出的請求方法(因為里面定義了請求攔截器,每次請求之前都會取出token并放到請求頭,這就又變成請求頭中攜帶的token無效了,導致重復發(fā)送刷新請求進入死循環(huán),所以不能這樣做)

          /**
           * 刷新token 
           * 成功返回新token
           * 失敗返回空字符串''
           */

          export async function refresh() : Promise<string>{
            const refreshToken = window.sessionStorage.getItem("refreshToken")
            console.log("in >>> " ,refreshToken)
            if(refreshToken == undefined)
                return '' //本來就沒有這個更新token則直接返回
            
            try {
              const response = await axios({
                method'GET',
                url'http://127.0.0.1:9001/api/simple/cloud/access/refresh',// 認證服務器地址
                headers: {
                  Authorization`Bearer ${refreshToken}`//header中放入的是refreshToken用于刷新請求
                },
              });
            // 如果順利返回會得到 data,由于后端使用統(tǒng)一結果返回ResultData,所以會多封裝一層code、data
              if (response.data) {
                return response.data.data; //所以這里有兩個data
              } else {
                return '';
              }
            } catch (error) {
              console.log(error);
              return '';
            }
          }  

          2.1.4 正常和刷新情況下的console輸出信息分析

          細心的讀者可以注意到上邊的代碼有很多地方有控制臺的輸出,加上這些可以更方便的讀懂代碼的邏輯,下面我們就運行代碼跑跑看看結果返回情況,這里建議各位結合代碼分析看看我做輸出的地方是在哪里。

          下圖是正常情況下的返回結果,注意這里的token是以hizFIGg結尾,而refreshToken是以suvm-EgQ結尾(這兩個注意與異常的來比對)正常情況下返回的結果肯定是200即ok

          注意>>>>>處輸出的結果是點擊該按鈕后點擊事件返回的結果,對應著Q3的思考,具體分析會結合失敗的例子來演示

          下面來看異常情況的分析,由于token太長了,所以拆分兩張圖片更容易看一點,從左邊的圖開始分析

          • 在發(fā)起第一次請求后,后端gateway攔截器報錯 511 (是不是就是對應上面case 511 此時應該用refresh token刷新)

          • in ?>> 進來refresh方法的邏輯,成功打印出refreshToken 以suvm-EgQ結尾(是不是跟上面refreshToken相同)

          • 緊接著就是 輸出 刷新token成功 此時返回的是刷新后的token,將其覆蓋新的token并重新發(fā)送請求

          到這里左圖分析完畢,進入右圖的分析(肯定有讀者疑惑你這黃色的warn咋不講)別急這塊我會和右圖的紅色error一起講解

          • 緊接上面,用新的token發(fā)送請求,此時在請求攔截器處捕獲到的token是不是就是更新好的 以V0dYcMA結尾,而refreshToken則以suvm-EgQ結尾(得出結論refreshToken用做刷新,但本身并不刷新)

          • 此時捕獲到Uncaught error status 511 這不就是我們一開始的報錯嗎? 其實就是這樣的,原來的按鈕點擊事件調用getAllUser方法已經結束!!! 返回的結果是error 即是這里的511(把左右三個有顏色的塊拼起來一起看就懂了)而由于refresh方法是異步調用的所以其執(zhí)行順序穿插在其中

          最后返回結果可以看到已經沒有上面注意部分提到的>>>>>輸出內容,令通過更新好的token發(fā)送二次請求得到的結果記作data,此時的data已經不能返回原來的getAllUser方法調用處,因為原來的方法已經結束,通俗點話說就是這樣的二次調用結果毫無意義,用戶還是需要刷新網頁或者二次點擊以獲取資源

          這就是Q3提出的思考,由于異步調用而非阻塞式的調用方式導致原方法提前終止,可以考慮換成阻塞式的調用refresh方式刷新token,但是這樣又會導致該次點擊的響應變慢,用戶體驗差(有更好想法的讀者可以在評論區(qū)一起討論)

          2.2 改進版本

          既然異步方法不得行,那能不能換種思路?不要在失敗的時候發(fā)送,而是提前檢查存在本地的token有沒有過期,當檢查token過期時間小于一個臨界點,則異步調用刷新token方法,更新現(xiàn)有的token信息,此時是不是就解決上面的問題,只要是服務器端gateway攔截到token失效的請求我都要求重新登錄。此時就引出一個定時器的概念

          TypeScript中,定時器主要是指通過setIntervalsetTimeout這兩個函數(shù)來實現(xiàn)的周期性或延時執(zhí)行代碼的功能。

          首先,setInterval是一個可以按照指定的時間間隔重復執(zhí)行某段代碼或函數(shù)的方法。它接受兩個參數(shù):第一個參數(shù)是你想要周期性執(zhí)行的函數(shù)或代碼塊,第二個參數(shù)是時間間隔,單位為毫秒。

          由于當setInterval被調用時,它會在指定的時間間隔后執(zhí)行給定的函數(shù)或代碼塊。這個時間間隔是以毫秒為單位的,而且它是從調用setInterval的那一刻開始計算的。這意味著一旦setInterval被調用,定時器就會立即啟動,并在每個指定的時間間隔后重復執(zhí)行。所以該定時器的設定應該放在login方法登錄返回結果處

          2.2.1 定義定時器類

          通過該定時器類,可以實現(xiàn)MyTimer.start 方法調用setInterval 間隔delay 時間步執(zhí)行,判斷當前的token過期時間是否小于我們設置的minCheck , 如果小于則使用refreshToken異步刷新token

          import { refresh } from "@/api/system/auth/index"
          import { jwtDecode } from "jwt-decode";

          export class MyTimer {
            private timerId: any | null = null;
            // delay為重復探查的間隔時間 , minCheck是判斷token是否是快過期的
             start(delay: number, minCheck : number): void {
               this.timerId = setInterval(async () => {
                 const currentToken = window.sessionStorage.getItem('token');
                 console.log("timer++++")
                 if (currentToken) {
                   // 如果存在token,判斷是否過期
                   let expirationTime = 0;
                   expirationTime = getExpirationTime(currentToken) ; // 假設有一個函數(shù)用于獲取token的過期時間
                   
                   const timeRemaining = expirationTime - Date.now();
                   if (timeRemaining <= minCheck) {
                     // 如果剩余時間小于等于5分鐘,則異步發(fā)送刷新請求并更新token
                     await refresh();
                   }
                 } else {
                   // 如果不存在token,則直接發(fā)送刷新請求并更新token
                   await refresh();
                 }
             }, delay);
             }
           
             stop(): void {
               if (this.timerId !== null) {
                 clearInterval(this.timerId);
                 this.timerId = null;
               }
             }
           }
          // 獲取過期時間
           function getExpirationTime(rawToken:string) : number{
             const res = jwtDecode(rawToken)
             
             return res.exp as number
           }

          2.2.2 修改Login點擊事件

          只用看新增的方法,其他的都是一些權限跟token等的存儲

          import { MyTimer } from "@/utils/tokenMonitor"

          const submit = () => {
            if (validate()) {
              login(formData)
                .then((data: UserInfoRes) => {
                  if (data) {
                    // 在這里添加需要執(zhí)行的操作
                    const token = data.token;

                    // 將token存儲到authStore中
                    const authStore = useAuthStore()
                    authStore.setToken(token)
                    window.sessionStorage.setItem('token', token)
                    window.sessionStorage.setItem('refreshToken', data.refreshToken)
                    authStore.setIsAuthenticated(true)
                    window.sessionStorage.setItem('isAuthenticated''true')
                    authStore.setName(data.name)
                    authStore.setButtons(data.buttons)
                    authStore.setRoles(data.roles)
                    authStore.setRouters(data.routers)
            //新增 引入計時器》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》
                    const clock = new MyTimer();
                    clock.start(1000*30,1000*30);

                    init({ message: "logged in success", color: 'success' });
                    push({ name: 'dashboard' })

                  }
                })
                .catch(() => {
                  init({ message: "logged in fail , please check carefully!", color: '#FF0000' });
                });

            }else{
              Message.error('error submit!!')
              return false
            }
          }

          2.2.3 測試

          按理來說測試時候應該沒有問題,能正確解析token,而實際運行時候卻報錯,無法正確解析token報錯

          InvalidTokenError: Invalid token specified: invalid json for part #2

          而后續(xù)換成jwt.verify()使用密鑰來解碼同樣報錯,甚至無法加載出頁面,console中報錯信息如下

          半天這token解析不了就很奇怪了,后面在網上查閱資料的過程中總結出來,由于后端生成的token是通過jjwt這個依賴實現(xiàn)的,對于不同的庫底層的編碼實現(xiàn)邏輯會有差異導致a庫加密生成的token并不能完全被b庫的方法來解密

          找到了原因,那我們應該如何獲取token中的過期時間呢?可以使用與jjwt相同的實現(xiàn)邏輯庫來解碼該token或者不妨換個思路,從服務器端下發(fā)token的時候我就帶上這個過期時間,這樣就省去了前端解碼這個步驟,所以就引出了如下最終實現(xiàn)版本

          2.3 最終定時器版本(實現(xiàn)可以直接看這里)

          2.3.1 服務器端修改

          2.3.1.1 根據token獲取其過期時間
          // 獲取當前token過期時間 這里不判斷是否過期因為是通過了過期判斷才進來的
          public static Date getExpirationDate(String token) {
              if(StringUtil.isBlank(token))
                  return null;

              Claims claims = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token).getBody();
              return claims.getExpiration();
          }
          2.3.1.2 發(fā)放token處攜帶過期時間
          //存放token到請求頭中
          String[] tokenArray = JWTHelper.createToken(sysUser.getId(), sysUser.getEmail(), permsList);
          map.put("token",tokenArray[0]);
          // 新增設置過期時間 毫秒數(shù)
          map.put("tokenExpire",JWTHelper.getExpirationDate(tokenArray[0]).getTime());
          map.put("refreshToken",tokenArray[1]);

          同樣在refreshToken處也就不是只返回token,也需要帶上其過期時間,代碼與上面相同就不重復寫了

          2.3.2 修改監(jiān)控器類MyTimer

          最終版本該類中包含這三個屬性,分別是

          • timerId: 定時器的唯一ID

          • delay: 定時器執(zhí)行的間隔時間

          • minCheck: 判斷token過期時間是否小于該值,小于則需執(zhí)行refresh() 方法來刷新token。

          同時使用單例模式全局導出唯一的實例方便管理,對于上面的token無法解析問題,直接從服務器端獲取token的過期時間expire然后與當前時間比較就好啦。

          import { refresh } from "@/api/system/auth/index"

          class MyTimer {
              private timerId: any | null = null;
              private delay: number; //執(zhí)行間隔時間
              private minCheck: number; //判斷token過期時間是否小于該值

              private static instance: MyTimer;

              public static getInstance(): MyTimer {
                if (!MyTimer.instance) {
                  MyTimer.instance = new MyTimer();
                }
                return MyTimer.instance;
              }

              private constructor() {
                this.delay = 30000// Default delay value in milliseconds
                this.minCheck = 60000// Default minCheck value in milliseconds (1 minutes)
              }
           //啟動監(jiān)控器的方法
              start(): void {
                this.timerId = setInterval(async () => {
                  const currentToken = window.sessionStorage.getItem('token');
                  console.log("timer++++",currentToken)
                  if (currentToken) {
                    // 如果存在token,判斷是否過期
                    const tokenExpireStr = window.sessionStorage.getItem('tokenExpire') as string// 假設有一個函數(shù)用于獲取token的過期時間
                    const expirationTime = parseInt(tokenExpireStr, 10); //以10進制轉換string字符串
                    const timeRemaining = expirationTime - Date.now();
                    console.log("ttime sub++++",timeRemaining)
                    if (timeRemaining <= this.minCheck) {
                      // 如果剩余時間小于等于minCheck分鐘,則異步發(fā)送刷新請求并更新token
                      try{
                        await refresh();
                      }catch (error) {
                        console.error('刷新失敗:', error);
                        window.sessionStorage.removeItem('isAuthenticated')
                        window.sessionStorage.removeItem('token')
                        window.sessionStorage.removeItem('refreshToken')
                        Message.error("token reflesh got some ploblem , please login")
                        // 跳轉到登錄頁的代碼
                        window.location.href = '/auth/login';
                      }
                    }
                  } else {
                    Message.error("token invalidate , please login")
                    // token不存在 則跳轉到登錄頁  
                    window.location.href = '/auth/login';
                  }
              }, this.delay);

              console.log(this.timerId)
              }
            //關閉監(jiān)控器的方法
              stop(): void {
                if (this.timerId !== null) {
                  clearInterval(this.timerId);
                  this.timerId = null;
                }
              }
           //提供設置監(jiān)控器的刷新間隔和需要刷新的閾值
              setDelay(delay: number): void {
                this.delay = delay;
              }
            
              setMinCheck(minCheck: number): void {
                this.minCheck = minCheck;
              }
            }
          //導出全局唯一的實例方便管理
           export const myFilterInstance = MyTimer.getInstance();
          // 加到每一個頁面上,當頁面刷新時候則重啟定時器,防止定時器刷掉
           export function onPageRender(){
              // Stop the current timer if it's running
              myFilterInstance.stop();

              // Start the timer with the updated delay and minCheck values
              myFilterInstance.start();
           }

          2.3.3 onPageRender 使用

          需要注意最后一個方法onPageRender,由于在測試中發(fā)現(xiàn)當通過導航欄訪問的頁面情況下會導致定時器給kill掉了,無法刷新token,發(fā)送新請求的時候就會報錯,所以最好的方法是在每個頁面上添加onPageRender 方法,該方法也很簡單就是重啟一下定時器,只要給定時器刷新token就能解決上面的問題

          在頁面中添加的代碼如下:

          import { onPageRender } from '@/utils/tokenMonitor'
          // 新增一個監(jiān)聽器,在頁面渲染時候執(zhí)行
          window.addEventListener('load', () => {
            onPageRender();
          });

          2.3.4 測試

          根據最終的測試結果(下圖,讀者可以結合代碼中輸出語句來看)

          • 可以看到紅色的框框就是進入監(jiān)控器輸出的內容,每次進入都會比對token的過期時間判斷是否小于閾值(刷新完后還會用新的過期時間繼續(xù)比較)

          • 當小于閾值(這里設置1min = 60000ms)則進入refresh邏輯,這個就是上面講到的內容,一樣樣的,這樣就保證每次刷新攜帶的token大概率都是最新的!!!??到此客戶端實現(xiàn)功能已經全部講完啦

          3. 服務器端實現(xiàn)

          這種實現(xiàn)方法是在gateway處做攔截判斷當前的token是否過期,如果過期則通過WebClient攜帶refreshToken異步發(fā)起請求到認證服務器更新,下面代碼實現(xiàn)了發(fā)起請求到獲取數(shù)據的過程,但是沒有實現(xiàn)原來請求的再發(fā)送(偷個懶,后面再來填坑)

          // 向認證服務器發(fā)送請求,獲取新的token
           Mono<ResultData> newTokenMono = WebClient.create().get()
                   .uri(buildUri(SecurityAccessConstant.WEB_REQUEST_TO_AUTH_URL+SecurityAccessConstant.REQUEST_REFRESH
                           , new String[]{"refreshToken", token}))
                   .retrieve()
                   .bodyToMono(ResultData.class);


           // 原子操作
           AtomicBoolean isPass = new AtomicBoolean(false);
           //訂閱數(shù)據
           newTokenMono.subscribe(resultData -> {
               if(resultData.getCode() == "200"){
                   exchange.getRequest().getHeaders().set(SecurityAccessConstant.HEADER_NAME_TOKEN,
                           SecurityAccessConstant.TOKEN_PREFIX + resultData.getData());
                   isPass.set(true);
               }

           }).dispose(); // 銷毀資源

           if(isPass.get()){
               // 如果成功獲取到資源(新token則發(fā)送新請求)
               return chain.filter(exchange.mutate().request().build());
           }

          4. 怎么選擇

          在服務器端實現(xiàn)的好處如下:
          • 安全性: 在服務器端進行token刷新可以更好地控制和保護token的安全性,避免將敏感信息暴露給客戶端
          • 減少客戶端邏輯: 客戶端無需過多關注token刷新邏輯,降低了客戶端的復雜性和維護成本。
          • 集中管理: 所有用戶的token刷新邏輯集中在服務器端,方便統(tǒng)一管理和調整。
          • 解決一致性問題: 用戶端刷新token可能導致不同客戶端之間的狀態(tài)不一致,比如一個設備刷新了token而另一個設備未刷新,可能會出現(xiàn)異常情況。
          而在客戶端實現(xiàn)的好處又如下:
          • 即時性: 客戶端自動監(jiān)控可以實現(xiàn)實時監(jiān)測token的有效性,并及時觸發(fā)刷新,確保用戶操作的流暢性和體驗。
          • 離線支持: 對于需要離線訪問或長時間不與服務器通信的應用場景,客戶端自動監(jiān)控可以更好地處理token失效情況。
          • 靈活性: 某些特定場景下,客戶端可能更容易實現(xiàn)對token狀態(tài)的監(jiān)控和處理,例如需要根據用戶行為動態(tài)調整token刷新策略等。
          • 減輕服務器壓力: 用戶端刷新token可以減少服務器負擔,尤其對于大量用戶同時刷新token時,可分散處理壓力。

          可見在不同的場景下實現(xiàn)的方法有所不同,要根據實際需求來決定,往往在一些高精度高安全性的系統(tǒng)中適合在服務器端做token的刷新,其他場景(例如移動端應用或簡單的 Web 應用等)下可以嘗試客戶端實現(xiàn)的方法分擔服務器壓力

          來源:blog.csdn.net/PleaseBeStrong/article/details/138967393


          到此文章就結束了。Java架構師必看一個集公眾號、小程序、網站(3合1的文章平臺,給您架構路上一臂之力)。如果今天的文章對你在進階架構師的路上有新的啟發(fā)和進步,歡迎轉發(fā)給更多人。歡迎加入架構師社區(qū)技術交流群,眾多大咖帶你進階架構師,在后臺回復“加群”即可入群。



          這些年小編給你分享過的干貨


          0.ChatGPT 4o 國內直接用 !!!

          1.idea2024.1.4永久激活碼(親測可用)

          2.優(yōu)質ERP系統(tǒng)帶進銷存財務生產功能(附源碼)

          3.優(yōu)質SpringBoot帶工作流管理項目(附源碼)

          4.最好用的OA系統(tǒng),拿來即用(附源碼)

          5.SBoot+Vue外賣系統(tǒng)前后端都有(附源碼

          6.SBoot+Vue可視化大屏拖拽項目(附源碼)


          轉發(fā)在看就是最大的支持??

          瀏覽 84
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美熟妇视频在线 | 插穴网站 | 爱爱综合网站 | 亚洲精品一二区 | 国产成人精品免费视频麻豆大全 |