欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

node.js實(shí)現(xiàn)雙Token+Cookie存儲+無感刷新機(jī)制的示例

 更新時間:2025年05月23日 09:23:05   作者:別看我只是一只楊女士1  
本文主要介紹了Node.js雙Token機(jī)制,以短期AccessToken和長期RefreshToken提升安全與體驗(yàn),結(jié)合Cookie存儲與自動刷新,實(shí)現(xiàn)無感登錄及多設(shè)備管理,感興趣的可以了解一下

為什么要實(shí)施雙token機(jī)制?

優(yōu)點(diǎn)描述
安全性Access Token 短期有效,降低泄露風(fēng)險(xiǎn);Refresh Token 權(quán)限受限,僅用于獲取新 Token
用戶體驗(yàn)用戶無需頻繁重新登錄,Token 自動刷新過程對用戶透明
靈活性獨(dú)立控制不同 Token 的生命周期,適應(yīng)各種場景需求
可管理性支持多設(shè)備登錄管理,便于撤銷特定設(shè)備的登錄狀態(tài)
性能優(yōu)化減少數(shù)據(jù)庫查詢次數(shù),提升系統(tǒng)響應(yīng)速度

實(shí)現(xiàn)方案:

模塊實(shí)現(xiàn)方式
登錄接口返回 accessToken 和 refreshToken,分別存入 Cookie
Access Token短時效 JWT,用于請求鑒權(quán)
Refresh Token長時效 JWT,用于刷新 Access Token
Token 校驗(yàn)方式后端從 Cookie 中讀取 token(即 Access Token)
前端 Axios使用響應(yīng)攔截器統(tǒng)一處理 Token 失效和自動刷新
  • 使用 JWT 生成兩個 Token:
    • Access Token(短時效):用于接口認(rèn)證,例如有效期為 15 分鐘
    • Refresh Token(長時效):用于刷新 Access Token,例如有效期為 7 天
  • 在用戶登錄時返回這兩個 Token,并將 Refresh Token 存儲在數(shù)據(jù)庫中
  • 當(dāng) Access Token 過期后,客戶端使用 Refresh Token 請求新的 Access Token
  • 如果 Refresh Token 也過期或無效,則強(qiáng)制重新登錄

具體代碼實(shí)現(xiàn)

1. 安裝依賴:

cookie-parser用來解析 Cookie 中的 Token

npm install jsonwebtoken bcryptjs cookie-parser

2. 數(shù)據(jù)庫添加兩個字段

refresh_tokenVARCHAR(255)加密后的 RefreshToken
expires_atDATETIMERefreshToken 過期時間

3. 在后端cors跨域中間中添加屬性

// 將cors注冊為全局中間件
app.use(cors({
  origin: 'http://localhost:5173', // 前端地址
  credentials: true // ?? 允許攜帶憑證(cookies)
}))

3. 登錄邏輯改造(添加雙token)

  • 添加配置文件config.js
module.exports = {
  jwtSecretKey: 'yke;eky1]239_jwt87-2up34',
  refreshTokenSecretKey: 'yke;eky1]239_refresh87-2up34',
  accessExpiresIn: '15m',  // 訪問令牌有效期
  refreshExpiresIn: '7d',   // 刷新令牌有效期
  accessExpiresInSec: 15 * 60,  // 秒數(shù)
  refreshExpiresInSec: 7 * 24 * 60 * 60  // 秒數(shù)
}
  • jwt生成accessToken訪問token、refreshToken刷新token
// 生成access token
const accessToken = jwt.sign(
  { id: user.id, username: user.username, email: user.email },
  config.jwtSecretKey,
  { expiresIn: config.accessExpiresIn }
)
// 生成refresh token
const refreshToken = jwt.sign(
  { id: user.id, username: user.username, email: user.email },
  config.refreshTokenSecretKey,
  { expiresIn: config.refreshExpiresIn }
)
  • 生成token過期時間,和refreshToken一起存入數(shù)據(jù)庫
 const expiresAt = new Date()
 expiresAt.setSeconds(expiresAt.getSeconds() + config.refreshExpiresInSec)
  • 將accessToken訪問token、refreshToken刷新token存入cookie
// 設(shè)置cookie
res.cookie('token', accessToken, {
    maxAge: config.accessExpiresInSec * 1000,
    httpOnly: true,
    secure: true,
    path: '/'
})
res.cookie('refresh_token', refreshToken, {
    maxAge: config.refreshExpiresInSec * 1000,
    httpOnly: true,
    secure: true,
    path: '/api/user/refresh-token',   // 限制路徑提高安全性
    sameSite: 'none'
})

登錄邏輯完整代碼:

// 用戶登錄的處理函數(shù)
exports.login = (req, res) => {
  // 接收表單數(shù)據(jù)
  const userInfo = req.body
  console.log(userInfo)
  // 查詢用戶信息
  const sqlStr_name = 'select * from user where username=?'
  db.query(sqlStr_name, [userInfo.username], (err, results) => {
    if (err) {
      return res.send({ status: 1, message: err })
    }
    // 執(zhí)行sql語句成功,但是獲取的條數(shù)不等于1
    if (results.length === 0) {
      return res.send({ status: 1, message: '該用戶不存在' })
    }
    // 判斷密碼是否正確
    const cmpresult = bcrypt.compareSync(userInfo.password, results[0].password)
    if (!cmpresult) {
      return res.send({ status: 1, message: '密碼錯誤' })
    }
    // 在服務(wù)器端生成Token字符串
    const user = { ...results[0] }
    // 生成access token
    const accessToken = jwt.sign(
      { id: user.id, username: user.username, email: user.email },
      config.jwtSecretKey,
      { expiresIn: config.accessExpiresIn }
    )
    // 生成refresh token
    const refreshToken = jwt.sign(
      { id: user.id, username: user.username, email: user.email },
      config.refreshTokenSecretKey,
      { expiresIn: config.refreshExpiresIn }
    )
    // 將refresh token存儲到數(shù)據(jù)庫中
    const expiresAt = new Date()
    expiresAt.setSeconds(expiresAt.getSeconds() + config.refreshExpiresInSec)
    const sqlStr_refreshToken = 'update user set refresh_token=?, expires_at=? where id=?'
    db.query(sqlStr_refreshToken, [refreshToken, expiresAt, user.id], (err) => {
      if (err) {
        console.error('保存refreshToken失敗:', err)
        return res.send({ status: 1, message: '保存refreshToken失敗' })
      }
      // 設(shè)置cookie
      res.cookie('token', accessToken, {
        maxAge: config.accessExpiresInSec * 1000,
        httpOnly: true,
        secure: true,
        path: '/'
      })
      res.cookie('refresh_token', refreshToken, {
        maxAge: config.refreshExpiresInSec * 1000,
        httpOnly: true,
        secure: true,
        path: '/api/user/refresh-token',   // 限制路徑提高安全性
        sameSite: 'none'
      })
      res.send({
        status: 0,
        message: '登錄成功',
        data: {
          username: results[0].username
        }
      })
    })  
  })
}

4. 實(shí)現(xiàn)token刷新接口

創(chuàng)建新路由/refreshToken

// token刷新接口
exports.refreshToken = (req, res) => {
  // 直接從cookie中獲取刷新token => 前端不需要再單獨(dú)把token傳入請求頭
  const refreshToken = req.cookies.refresh_token
  // 判斷refresh token是否存在
  if (!refreshToken) {
    return res.send({ status: 1, message: '缺少refreshToken,請先登錄' })
  }
  try {
    // 驗(yàn)證refreshToken
    const decoded = jwt.verify(refreshToken, config.refreshTokenSecretKey)
    // 查詢用戶是否存在且refreshToken匹配
    const sql = 'select * from user where id=? and refresh_token=?'
    db.query(sql, [decoded.id, refreshToken], (err, results) => {
      if (err) {
        return res.send({ status: 1, message: '無效的refreshToken' + err.message })
      }
      const user = results[0]
      // 生成新的access token
      const accessToken = jwt.sign(
        { id: user.id, username: user.username, email: user.email },
        config.jwtSecretKey,
        { expiresIn: config.accessExpiresIn }
      )
      // 更新accessToken到Cookie
      res.cookie('token', accessToken, {
        maxAge: config.accessExpiresInSec * 1000,
        httpOnly: true,
        secure: true,
        path: '/'
      })
      res.send({
        status: 0,
        message: 'accessToken刷新成功',
        data: {
          token: accessToken
        }
      })
    })
  } catch (error) {
    return res.status(403).send({ status: 1, message: 'token已過期,請重新登錄' })
  }
}

5. 響應(yīng)攔截器中處理token

import axios from 'axios'
import { message } from 'antd'
import { refreshTokenService } from '@/api/user'

const instance = axios.create({
  baseURL: 'http://localhost:3333',  // 你的API服務(wù)器地址
  timeout: 10000,  // 請求超時時間
  headers: {
    'Content-Type': 'application/json'
  },
  // 必須加上這個選項(xiàng)才能跨域攜帶
  withCredentials: true
})

// 添加請求攔截器
instance.interceptors.request.use(
  (config) => {
    // 后端將token存在了cookie中,這里不需要攜帶token
    return config
  },
  (err) => Promise.reject(err)
)

// 標(biāo)記是否正在刷新 Token(防止并發(fā)刷新)
let isRefreshing = false
// 保存所有因 Token 失效而等待新 Token 的請求回調(diào)函數(shù)
let refreshSubscribers = []
// 成功獲取到新的 Token 后,執(zhí)行所有等待的請求
function onRefreshed(newToken) {
  refreshSubscribers.forEach((cb) => cb(newToken))
  refreshSubscribers = []
}
// 將等待刷新 Token 的請求封裝成一個回調(diào)函數(shù),加入隊(duì)列中
function addRefreshSubscriber(callback) {
  refreshSubscribers.push(callback)
}
// 響應(yīng)攔截器
instance.interceptors.response.use(
  (res) => {
    console.log(res) 
    // 摘取核心響應(yīng)數(shù)據(jù)
    if (res.data.status === 0) {
      return res
    }
    // 處理業(yè)務(wù)失敗
    message.error({type: 'error', content: res.data.message || '服務(wù)異常'})
    return Promise.reject(res.data)
  },
  async (err) => {
    // 錯誤的特殊情況 => 401權(quán)限不足或token過期 => 攔截到登錄
    const originalRequest = err.config
    //  判斷是否是 401 并且不是已經(jīng)重試過的請求
    if (err.response?.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true
      // 控制 Token 刷新流程(防止多次刷新)
      if (!isRefreshing) {
        // 標(biāo)記刷新狀態(tài)
        isRefreshing = true
        try {
          const res = await refreshTokenService()
          const newToken = res.data.data.token
          // 重試請求
          onRefreshed(newToken)
        } catch {
          // 刷新失敗
          message.error({ type: 'error', content: '登錄已過期,請重新登錄' })
          // 跳轉(zhuǎn)登錄
          if (window.location.pathname !== '/login') {
            history.push('/login')
          }
        } finally {
          isRefreshing = false
        }
      }
      // 把當(dāng)前請求放入隊(duì)列,等待 Token 刷新后再重發(fā)
      return new Promise((resolve) => {
        addRefreshSubscriber((newToken) => {
          originalRequest.headers['Authorization'] = `Bearer ${newToken}`
          resolve(instance(originalRequest))
        })
      })
    } else {
      // 錯誤的默認(rèn)情況 =》 只給提示
      message.error({ type: 'error', content: err.response.data.message || '服務(wù)異常' })
    }
    return Promise.reject(err)
  }
)

export default instance

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

相關(guān)文章

  • 快速掌握Node.js事件驅(qū)動模型

    快速掌握Node.js事件驅(qū)動模型

    這篇文章主要為大家詳細(xì)介紹了Node.js事件驅(qū)動模型,首先了解一下傳統(tǒng)的線程網(wǎng)絡(luò)模型,然后再學(xué)習(xí)了解Node.js事件驅(qū)動模型,感興趣的小伙伴們可以參考一下
    2016-03-03
  • Centos7 安裝Node.js10以上版本的方法步驟

    Centos7 安裝Node.js10以上版本的方法步驟

    這篇文章主要介紹了Centos7 安裝Node.js10以上版本的方法步驟,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-10-10
  • 使用nvm安裝node?v12.22.12時遇到的問題解決

    使用nvm安裝node?v12.22.12時遇到的問題解決

    本文介紹了使用nvm安裝node?v12.22.12時遇到的問題解決,解決了上手動下載和安裝npm以解決版本不匹配的問題,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-12-12
  • 深入解析koa之中間件流程控制

    深入解析koa之中間件流程控制

    這篇文章主要介紹了深入解析koa之中間件流程控制,koa被認(rèn)為是第二代web后端開發(fā)框架,相比于前代express而言,其最大的特色無疑就是解決了回調(diào)金字塔的問題,讓異步的寫法更加的簡潔。,需要的朋友可以參考下
    2019-06-06
  • 進(jìn)階之初探nodeJS

    進(jìn)階之初探nodeJS

    本文主要介紹了nodeJS的相關(guān)知識。具有很好的參考價(jià)值,下面跟著小編一起來看下吧
    2017-01-01
  • nodejs基于express實(shí)現(xiàn)文件上傳的方法

    nodejs基于express實(shí)現(xiàn)文件上傳的方法

    這篇文章主要介紹了nodejs基于express實(shí)現(xiàn)文件上傳的方法,結(jié)合實(shí)例形式分析了nodejs基于express框架實(shí)現(xiàn)文件上傳功能的具體步驟與相關(guān)操作技巧,需要的朋友可以參考下
    2018-03-03
  • Nodejs關(guān)于gzip/deflate壓縮詳解

    Nodejs關(guān)于gzip/deflate壓縮詳解

    本文主要向大家介紹了nodejs中關(guān)于gzip/deflate壓縮的2種方法,分別是管道壓縮和非管道壓縮,十分詳細(xì),并附帶示例,這里推薦給大家參考下。
    2015-03-03
  • Nodejs解析網(wǎng)站網(wǎng)址內(nèi)容并獲取標(biāo)題圖標(biāo)

    Nodejs解析網(wǎng)站網(wǎng)址內(nèi)容并獲取標(biāo)題圖標(biāo)

    cheerio類似于jQuery的API,讓我們可以方便地操作HTML文檔,下面我們就來看看在Node.js中如何借助cheerio庫高效地解析和提取HTML內(nèi)容吧
    2024-11-11
  • Node.js中Swagger的使用指南詳解

    Node.js中Swagger的使用指南詳解

    Swagger(目前用OpenAPI?Specification代替)是一個用于設(shè)計(jì)、構(gòu)建、記錄和使用REST?API的強(qiáng)大工具,本文將探討使用Swagger的一些關(guān)鍵技巧,需要的可以參考一下
    2024-01-01
  • 基于NodeJS的前后端分離的思考與實(shí)踐(二)模版探索

    基于NodeJS的前后端分離的思考與實(shí)踐(二)模版探索

    在傳統(tǒng)的開發(fā)模式中,瀏覽器端與服務(wù)器端是由不同的前后端兩個團(tuán)隊(duì)開發(fā),但是模版卻又在這兩者中間的模糊地帶。因此模版上面總不可避免的越來越多復(fù)雜邏輯,最終難以維護(hù)。
    2014-09-09

最新評論