最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

雙Token實現(xiàn)無感刷新的完整代碼示例

 更新時間:2025年06月14日 10:31:40   作者:憶柒  
這篇文章主要介紹了雙Token實現(xiàn)無感刷新的相關(guān)資料,通過雙Token(access_token短效,refresh_token長效)實現(xiàn)用戶身份自動續(xù)期,避免頻繁登錄,文中通過代碼介紹的非常詳細,需要的朋友可以參考下

一、為什么需要無感刷新?

想象一下你正在刷視頻,突然提示"登錄已過期,請重新登錄",需要退出當前頁面重新輸入密碼。這樣的體驗非常糟糕!無感刷新就是為了解決這個問題:讓用戶在不知不覺中完成身份續(xù)期,保持長時間在線狀態(tài)。

二、雙Token機制原理

我們使用兩個令牌:

  • 短令牌:access_token(1小時):用于日常請求
  • 長令牌:refresh_token(7天):專門用來刷新令牌

工作流程:

用戶登錄 → 獲取雙令牌 → access_token過期 → 用refresh_token獲取新的雙令牌 → 自動續(xù)期

三、前端實現(xiàn)(Vue + Axios)

1. 登錄存儲令牌

const login = async () => {
  const res = await userLogin(user); //賬號密碼
  // 保存雙令牌到本地
  localStorage.setItem('access_token', res.access_token);
  localStorage.setItem('refresh_token', res.refresh_token);
}

2. 請求自動攜帶令牌

通過請求攔截器自動添加認證頭:

api.interceptors.request.use(config => {
  const access_token = localStorage.getItem('access_token');
  if (access_token) {
    config.headers.Authorization = `Bearer ${access_token}`;
  }
  return config;
})

3. 智能令牌刷新

響應(yīng)攔截器發(fā)現(xiàn)401登錄過期的錯誤時自動請求刷新

驗證長令牌是否失效

  • 失效重定向到登錄頁面
  • 未失效重新獲取雙令牌并重新發(fā)起請求
api.interceptors.response.use(
  (response) => {
    return response
  },
  async (error) => {  // 響應(yīng)失敗
    const { data, status, config } = error.response;
    if (status === 401 && config.url !== '/refresh') {
      // 刷新token
      const res = await refreshToken()  // 校驗的函數(shù)
      if (res.status === 200) {  // token刷新成功
        // 重新將剛剛失敗的請求發(fā)送出去
        return api(config)
      } else {
        // 重定向到登錄頁  router.push('/login')
        window.location.href = '/login'
      }
    }
  }
)

四、后端實現(xiàn)(Node.js + Express)

1. 生成雙令牌

// 生成1小時有效的access_token
const access_token = generateToken(user, '1h');
// 生成7天有效的refresh_token
const refresh_token = generateToken(user, '7d');

2. 令牌刷新接口

app.get('/refresh', (req, res) => {
  const oldRefreshToken = req.query.token;
  try {
    // 驗證refresh_token有效性
    const userData = verifyToken(oldRefreshToken);
    // 生成新雙令牌
    const newAccessToken = generateToken(userData, '1h');
    const newRefreshToken = generateToken(userData, '7d');
    res.json({ access_token: newAccessToken, refresh_token: newRefreshToken });
  } catch (error) {
    res.status(401).send('令牌已失效');
  }
})

五、完整代碼

1. 前端代碼

<template>
  <div v-if="!isLogin">
    <button @click="login">登錄</button>
  </div>

  <div v-else>
    <h1>登錄成功</h1>
    <p>歡迎回來,{{ username }}</p>
    <p>您的郵箱:{{ email }}</p>
  </div>


  <!-- home -->
   <div v-if="isLogin">
    <button @click="getHomeData">獲取首頁數(shù)據(jù)</button>
   </div>
</template>

<script setup>
import { ref } from 'vue'
import { userLogin, getHomeDataApi } from './api.js'

const isLogin = ref(false)
const username = ref('')
const email = ref('')
const password = ref('')


const login = async() => {
  username.value = 'zs'
  email.value = '123@qq.com'
  password.value = '123'

  const res = await userLogin({username: username.value, email: email.value, password: password.value})
  console.log(res)
  const {access_token, refresh_token, userInfo} = res.data
  if (access_token) {
    isLogin.value = true
  }
  localStorage.setItem('access_token', access_token)
  localStorage.setItem('refresh_token', refresh_token)
}


const getHomeData = async() => {
  const res = await getHomeDataApi()
  console.log(res)
}


</script>

<style lang="css" scoped>

</style> 
// api.js
import axios from 'axios'

const api = axios.create({
  baseURL: 'http://localhost:3000',
  timeout: 3000,  
})

// 請求攔截器
api.interceptors.request.use(config => {
  const access_token = localStorage.getItem('access_token');
  if (access_token) {
    config.headers.Authorization = `Bearer ${access_token}`;
  }
  return config;
})

// 響應(yīng)攔截器
api.interceptors.response.use(
  (response) => {
    return response
  },
  async (error) => {  // 響應(yīng)失敗
    const { data, status, config } = error.response;
    if (status === 401 && config.url !== '/refresh') {
      // 刷新token
      const res = await refreshToken()
      if (res.status === 200) {  // token刷新成功
        // 重新將剛剛失敗的請求發(fā)送出去
        return api(config)
      } else {
        // 重定向到登錄頁  router.push('/login')
        window.location.href = '/login'
      }
    }
  }
)


export const userLogin = (data) => {
  return api.post('/login', data)
}

export const getHomeDataApi = () => {
  return api.get('/home')
}

async function refreshToken() {
  const res = await api.get('/refresh', {
    params: {
      token: localStorage.getItem('refresh_token')
    }
  })
  localStorage.setItem('access_token', res.data.access_token)
  localStorage.setItem('refresh_token', res.data.refresh_token)
  return res
}

2. 后端代碼

server.js
const express = require('express');
const app = express();
const port = 3000;
app.use(express.json());  // 解析 JSON 格式的請求體
const jwtToken = require('./token.js');
const cors = require('cors');

app.use(cors())


const users = [
  { username: 'zs', password: '123', email: '123@qq.com' },
  { username: 'ls', password: '456', email: '456@qq.com' }
]




app.get('/', (req, res) => {
  res.send('Hello World!');
});

app.post('/login', (req, res) => {
  const { username, password } = req.body;
  const user = users.find(user => user.username === username);
  if (!user) {
    return res.status(404).json({status: 'error', message: '用戶不存在'});
  }
  if (user.password !== password) {
    return res.status(401).json({status: 'error', message: '密碼錯誤'});
  }

  // 生成兩個 token
  const access_token = jwtToken.generateToken(user, '1h');
  const refresh_token = jwtToken.generateToken(user, '7d');

  res.json({
    userInfo: {
      username: user.username,
      email: user.email
    },
    access_token,
    refresh_token
  })


})

// 需要token 認證的路由
app.get('/home', (req, res) => {
  const authorization = req.headers.authorization;
  if (!authorization) {
    return res.status(401).json({status: 'error', message: '未登錄'});
  }

  try {
    const token = authorization.split(' ')[1];  // 'Bearer esdadfadadxxxxxxxxx'
    const data = jwtToken.verifyToken(token);
    res.json({ status: 'success', message: '驗證成功', data: data });
  } catch (error) {
    return res.status(401).json({status: error, message: 'token失效,請重新登錄'});
  }

})

// 刷新 token
app.get('/refresh', (req, res) => {
  const { token } = req.query;

  try {
    const data = jwtToken.verifyToken(token);
    const access_token = jwtToken.generateToken(data, '1h');
    const refresh_token = jwtToken.generateToken(data, '7d');
    res.json({ status: 'success', message: '刷新成功', access_token, refresh_token });
  } catch (error) {
    return res.status(401).json({status: error, message: 'token失效,請重新登錄'});
  }
})

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`);
})
// token.js
const jwt = require('jsonwebtoken');

// 生成 token
function generateToken(user, expiresIn) {
  const payload = {
    username: user.username,
    email: user.email
  };
  const secret = 'my_secret_key';
  const options = {
    expiresIn: expiresIn
  };
  return jwt.sign(payload, secret, options);
}

// 驗證 token
function verifyToken(token) {
  const secret = 'my_secret_key';
  const decoded = jwt.verify(token, secret);
  return decoded;
}

module.exports = {
  generateToken,
  verifyToken
};

六、流程圖解

用戶發(fā)起請求 → 攜帶access_token → 服務(wù)端驗證
       ↓ 無效/過期
觸發(fā)401錯誤 → 前端攔截 → 發(fā)起refresh_token刷新請求
       ↓ 刷新成功
更新本地令牌 → 重新發(fā)送原請求 → 用戶無感知
       ↓ 刷新失敗
跳轉(zhuǎn)登錄頁面 → 需要重新認證

七、安全注意事項

  • refresh_token要長期有效,但也不能太長:通常設(shè)置7-30天有效期
  • 使用HTTPS:防止令牌被中間人竊取
  • 不要明文存儲令牌:使用瀏覽器localStorage要確保XSS防護
  • 設(shè)置合理有效期:根據(jù)業(yè)務(wù)需求平衡安全與體驗

到此這篇關(guān)于雙Token實現(xiàn)無感刷新的文章就介紹到這了,更多相關(guān)雙Token實現(xiàn)無感刷新內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • JScript面向事件驅(qū)動的編程

    JScript面向事件驅(qū)動的編程

    JScript面向事件驅(qū)動的編程...
    2007-01-01
  • [Bootstrap-插件使用]Jcrop+fileinput組合實現(xiàn)頭像上傳功能實例代碼

    [Bootstrap-插件使用]Jcrop+fileinput組合實現(xiàn)頭像上傳功能實例代碼

    這篇文章主要介紹了[Bootstrap-插件使用]Jcrop+fileinput組合實現(xiàn)頭像上傳功能實例代碼,非常具有實用價值,需要的朋友可以參考下。
    2016-12-12
  • 詳解webpack+gulp實現(xiàn)自動構(gòu)建部署

    詳解webpack+gulp實現(xiàn)自動構(gòu)建部署

    這篇文章主要介紹了詳解webpack+gulp實現(xiàn)自動構(gòu)建部署,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-06-06
  • Next.js腳手架完整搭建封裝的方法步驟

    Next.js腳手架完整搭建封裝的方法步驟

    本文主要介紹了Next.js腳手架完整搭建封裝的方法步驟,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2022-04-04
  • 微信公眾號開發(fā)之微信支付代碼記錄的實現(xiàn)

    微信公眾號開發(fā)之微信支付代碼記錄的實現(xiàn)

    這篇文章主要介紹了微信公眾號開發(fā)之微信支付代碼記錄的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-10-10
  • 前端如何計算首屏及白屏時間代碼示例

    前端如何計算首屏及白屏時間代碼示例

    白屏時間是指用戶進入該網(wǎng)站(比如刷新頁面、跳轉(zhuǎn)到新頁面等通過該方式)的時刻開始計算,一直到頁面內(nèi)容顯示出來之前的時間節(jié)點,這篇文章主要給大家介紹了關(guān)于前端如何計算首屏及白屏時間的相關(guān)資料,需要的朋友可以參考下
    2024-07-07
  • openlayers實現(xiàn)地圖彈窗

    openlayers實現(xiàn)地圖彈窗

    這篇文章主要為大家詳細介紹了openlayers實現(xiàn)地圖彈窗,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-09-09
  • JavaScript實現(xiàn)電燈開關(guān)小案例

    JavaScript實現(xiàn)電燈開關(guān)小案例

    這篇文章主要為大家詳細介紹了JavaScript實現(xiàn)電燈開關(guān)小案例,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-03-03
  • 一文詳解DOM的概念和常用操作

    一文詳解DOM的概念和常用操作

    本文詳細介紹了DOM的概念和常用操作,文檔對象模型 (DOM) 是 HTML 和 XML 文檔的編程接口。它提供了對文檔的結(jié)構(gòu)化的表述,并定義了一種方式可以使從程序中對該結(jié)構(gòu)進行訪問,從而改變文檔的結(jié)構(gòu),樣式和內(nèi)容,感興趣的朋友可以參考閱讀本文
    2023-04-04
  • canvas實現(xiàn)鐘表效果

    canvas實現(xiàn)鐘表效果

    本文主要分享了canvas實現(xiàn)鐘表效果的示例代碼。具有很好的參考價值,下面跟著小編一起來看下吧
    2017-02-02

最新評論

六盘水市| 扶余县| 淮滨县| 永宁县| 夏邑县| 砀山县| 信丰县| 龙岩市| 阿鲁科尔沁旗| 多伦县| 呼图壁县| 金塔县| 兴安县| 合江县| 青田县| 新巴尔虎左旗| 治县。| 农安县| 盐源县| 旌德县| 辉县市| 本溪| 新乐市| 桂林市| 凤冈县| 牡丹江市| 额敏县| 邯郸县| 永顺县| 宽甸| 潜山县| 白水县| 东乌珠穆沁旗| 浪卡子县| 揭阳市| 观塘区| 安图县| 星子县| 澎湖县| 岳阳市| 怀柔区|