You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

VueJS中存储用户token的最佳方案是什么(不使用localStorage和cookie)

方案选型结论

如果要求完全不使用localStorage和cookie,没有100%和本地存储等价的永久持久化方案,可选的最优替代方案有两类,可根据你的业务场景选择:

  • 方案1:IndexedDB加密存储 + 内存缓存(安全性优于localStorage,基本满足常规持久化需求)
  • 方案2:纯内存存储 + 服务端静默授权(安全性最高,无本地存储泄露风险,需要服务端配合)

方案1:IndexedDB加密存储

核心设计思路:对存储的token做对称加密,加密密钥仅保存在运行时内存中,就算XSS攻击读取到IndexedDB的加密数据,没有内存中的密钥也无法还原有效token,安全等级远高于明文存localStorage。

1. 加密工具函数示例

// 加密密钥在应用启动时生成,仅存在内存中,页面刷新后自动重置
const AES_KEY = crypto.getRandomValues(new Uint8Array(16)); // 128位密钥

// 加密函数
async function encrypt(data) {
  const encoder = new TextEncoder();
  const dataBuf = encoder.encode(data);
  const iv = crypto.getRandomValues(new Uint8Array(12));
  const key = await crypto.subtle.importKey('raw', AES_KEY, { name: 'AES-GCM' }, false, ['encrypt']);
  const encryptedBuf = await crypto.subtle.encrypt({ name: 'AES-GCM', iv: iv }, key, dataBuf);
  return btoa(JSON.stringify({
    iv: Array.from(iv),
    data: Array.from(new Uint8Array(encryptedBuf))
  }));
}

// 解密函数
async function decrypt(encryptedStr) {
  const { iv: ivArr, data: dataArr } = JSON.parse(atob(encryptedStr));
  const iv = new Uint8Array(ivArr);
  const dataBuf = new Uint8Array(dataArr);
  const key = await crypto.subtle.importKey('raw', AES_KEY, { name: 'AES-GCM' }, false, ['decrypt']);
  const decryptedBuf = await crypto.subtle.decrypt({ name: 'AES-GCM', iv: iv }, key, dataBuf);
  return new TextDecoder().decode(decryptedBuf);
}

2. IndexedDB简易封装示例

class TokenDB {
  constructor() {
    this.dbName = 'AppTokenDB';
    this.storeName = 'tokenStore';
    this.db = null;
  }

  init() {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open(this.dbName, 1);
      request.onupgradeneeded = (e) => {
        const db = e.target.result;
        if (!db.objectStoreNames.contains(this.storeName)) {
          db.createObjectStore(this.storeName, { keyPath: 'id' });
        }
      };
      request.onsuccess = (e) => {
        this.db = e.target.result;
        resolve();
      };
      request.onerror = (e) => reject(e.target.error);
    });
  }

  async saveToken(token) {
    const encryptedToken = await encrypt(token);
    const transaction = this.db.transaction(this.storeName, 'readwrite');
    const store = transaction.objectStore(this.storeName);
    store.put({ id: 'accessToken', value: encryptedToken });
  }

  async getToken() {
    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction(this.storeName, 'readonly');
      const store = transaction.objectStore(this.storeName);
      const request = store.get('accessToken');
      request.onsuccess = async (e) => {
        if (e.target.result) {
          try {
            const token = await decrypt(e.target.result.value);
            resolve(token);
          } catch (err) {
            // 解密失败说明密钥失效,清除无效数据
            this.clearToken();
            resolve(null);
          }
        } else {
          resolve(null);
        }
      };
      request.onerror = (e) => reject(e.target.error);
    });
  }

  clearToken() {
    const transaction = this.db.transaction(this.storeName, 'readwrite');
    const store = transaction.objectStore(this.storeName);
    store.delete('accessToken');
  }
}

3. Vuex集成示例

// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)
const tokenDB = new TokenDB();
// 应用启动时初始化DB
tokenDB.init();

export default new Vuex.Store({
  state: {
    token: null
  },
  mutations: {
    SET_TOKEN(state, token) {
      state.token = token;
    }
  },
  actions: {
    // 登录成功后存储token
    async saveTokenAfterLogin({ commit }, token) {
      commit('SET_TOKEN', token);
      await tokenDB.saveToken(token);
    },
    // 应用初始化时读取token
    async initToken({ commit }) {
      const token = await tokenDB.getToken();
      if (token) commit('SET_TOKEN', token);
    },
    // 退出登录时清除token
    async logout({ commit }) {
      commit('SET_TOKEN', null);
      tokenDB.clearToken();
    }
  }
})

4. 应用入口初始化

// main.js
import store from './store'

async function initApp() {
  await store.dispatch('initToken');
  new Vue({
    store,
    render: h => h(App)
  }).$mount('#app')
}
initApp();

方案2:纯内存存储 + 静默授权

完全不在本地存储任何token数据,安全性最高,适合对安全要求极高的业务场景,需要服务端配合支持静默授权流程。

流程说明

  1. 登录成功后,服务端返回token的同时,种下一个仅HttpOnly、Secure、SameSite=Strict的会话标记cookie(该cookie仅用作静默授权凭证,不会存token本身,HttpOnly属性可防止XSS窃取,不属于前端可操作的cookie范畴)
  2. token仅存在Vuex的内存中,页面刷新后,前端主动调用服务端的静默授权接口,服务端验证会话标记cookie有效后,直接返回新的token,前端再存入Vuex即可,整个过程用户无感知
  3. 退出登录时,调用服务端接口销毁会话标记,同时清空Vuex里的token

代码示例

// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'

Vue.use(Vuex)
export default new Vuex.Store({
  state: {
    token: null,
    isAuthed: false
  },
  mutations: {
    SET_TOKEN(state, token) {
      state.token = token;
      state.isAuthed = !!token;
    }
  },
  actions: {
    // 登录逻辑
    async login({ commit }, loginForm) {
      const res = await axios.post('/api/login', loginForm);
      commit('SET_TOKEN', res.data.token);
    },
    // 静默授权逻辑
    async silentAuth({ commit }) {
      try {
        const res = await axios.post('/api/silent-auth');
        commit('SET_TOKEN', res.data.token);
        return true;
      } catch (err) {
        commit('SET_TOKEN', null);
        return false;
      }
    },
    // 退出登录
    async logout({ commit }) {
      await axios.post('/api/logout');
      commit('SET_TOKEN', null);
    }
  }
})

应用入口初始化

// main.js
import store from './store'
import router from './router'

async function initApp() {
  const isAuthed = await store.dispatch('silentAuth');
  // 未授权且访问需鉴权页面时跳转到登录页
  if (!isAuthed && router.currentRoute.meta.requireAuth) {
    router.push('/login');
  }
  new Vue({
    store,
    router,
    render: h => h(App)
  }).$mount('#app')
}
initApp();

注意事项

  • 方案1的加密密钥每次页面刷新都会重新生成,所以如果用户完全关闭浏览器再打开,IndexedDB里的加密token会解密失败,需要重新登录,属于刻意设计的安全策略,避免设备丢失后token被窃取
  • 如果你完全不能接受任何cookie,只能选择方案1

内容的提问来源于stack exchange,提问作者Peter Van de Put

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.30 08:54:03