前端存储(vue typescript 登录状态,如何存储)

前端存储(vue typescript 登录状态,如何存储)
vue typescript 登录状态,如何存储

在 Vue + TypeScript 项目中,登录状态的存储核心是客户端存储标识 + 服务端验证,结合 Vue 生态的最佳实践,我会分「存储方案选择」「TS 类型封装」「实际代码实现」三部分讲清楚,保证类型安全且符合前端工程化规范。


一、核心存储方案选择(按场景)

1. 首选:Cookie(推荐登录态)

  • 存储位置:浏览器 Cookie(通过 js-cookie 库操作)
  • 优势: 支持 HttpOnly/Secure 配置,防 XSS 攻击(服务端设置更安全); 自动随请求携带,无需手动拼接,适配后端 Session 验证; 可设置过期时间,天然支持登录态超时。
  • 适用:服务端维护 Session(Redis 存储)的场景,前端只需存 Session ID。

2. 次选:LocalStorage/SessionStorage(适配 JWT)

  • LocalStorage:永久存储(除非手动删除),适合长期登录态;
  • SessionStorage:会话级存储(关闭标签页清空),适合临时登录态;
  • 优势:存储容量大(5MB),可存更多用户信息;
  • 注意:易受 XSS 攻击,不要存敏感信息(如 token 需加密 / 服务端验证)。

3. 补充:Vue Pinia(内存缓存)

  • 登录状态同步到 Pinia 全局状态,解决页面刷新后状态丢失问题(需结合 Cookie/LocalStorage 持久化);
  • 优势:响应式,组件间共享登录态更便捷,TS 类型支持完善。

二、完整实现步骤(Vue3 + TS + Pinia + Cookie)

前置依赖安装

bash

运行

# 操作 Cookie 的库(TS 友好)npm install js-cookie @types/js-cookie# Pinia(Vue 官方状态管理,替代 Vuex)npm install pinia

步骤 1:封装 TS 类型(保证类型安全)

新建 src/types/user.ts,定义用户和登录状态类型:

typescript

运行

// 用户信息类型export interface UserInfo {  userId: number;  username: string;  token: string; // 或 Session ID  roles: string[]; // 权限角色}// 登录请求参数类型export interface LoginParams {  username: string;  password: string;}

步骤 2:封装存储工具(Cookie/LocalStorage)

新建 src/utils/storage.ts,统一处理存储逻辑,避免重复代码:

typescript

运行

import Cookies from 'js-cookie';import { UserInfo } from '@/types/user';// 存储键名(统一管理,避免硬编码)const STORAGE_KEYS = {  TOKEN: 'USER_TOKEN',  USER_INFO: 'USER_INFO',  LOGIN_STATUS: 'IS_LOGIN'};// ==================== Cookie 操作(推荐存储 Token/Session ID) ====================/** * 设置 Token(Cookie) * @param token 令牌 * @param expires 过期时间(天),默认 7 天 */export const setToken = (token: string, expires = 7) => {  Cookies.set(STORAGE_KEYS.TOKEN, token, {    expires,    path: '/', // 全站有效    // 生产环境开启(仅 HTTPS 传输)    // secure: process.env.NODE_ENV === 'production',    // 防 XSS(需服务端设置,前端设置无效)    // httpOnly: true   });};/** * 获取 Token(Cookie) */export const getToken = (): string | undefined => {  return Cookies.get(STORAGE_KEYS.TOKEN);};/** * 删除 Token(Cookie) */export const removeToken = () => {  Cookies.remove(STORAGE_KEYS.TOKEN);};// ==================== LocalStorage 操作(存储用户信息) ====================/** * 设置用户信息(LocalStorage,TS 类型校验) */export const setUserInfo = (userInfo: UserInfo) => {  localStorage.setItem(STORAGE_KEYS.USER_INFO, JSON.stringify(userInfo));};/** * 获取用户信息(LocalStorage,TS 类型转换) */export const getUserInfo = (): UserInfo | null => {  const info = localStorage.getItem(STORAGE_KEYS.USER_INFO);  if (info) {    try {      return JSON.parse(info) as UserInfo;    } catch (e) {      console.error('解析用户信息失败', e);      return null;    }  }  return null;};/** * 删除用户信息 */export const removeUserInfo = () => {  localStorage.removeItem(STORAGE_KEYS.USER_INFO);};// ==================== 登录状态快捷操作 ====================/** * 清空所有登录态 */export const clearLoginState = () => {  removeToken();  removeUserInfo();};

步骤 3:Pinia 全局状态管理(响应式登录态)

新建 src/stores/user.ts,封装登录 / 登出 / 状态同步逻辑:

typescript

运行

import { defineStore } from 'pinia';import { ref, computed } from 'vue';import { UserInfo, LoginParams } from '@/types/user';import { setToken, getToken, setUserInfo, getUserInfo, clearLoginState } from '@/utils/storage';// 假设你有封装的请求库import { request } from '@/utils/request';// 定义 Pinia Store,保证 TS 类型提示export const useUserStore = defineStore('user', () => {  // 响应式状态:用户信息  const userInfo = ref(null);  // 响应式状态:是否登录  const isLogin = computed(() => !!userInfo.value && !!getToken());  // 初始化:页面刷新后从本地存储恢复登录态  const initUserState = () => {    const info = getUserInfo();    if (info && getToken()) {      userInfo.value = info;    }  };  // 登录操作(异步,TS 类型校验)  const login = async (params: LoginParams) => {    try {      // 调用登录接口,获取 Token 和用户信息      const res = await request.post<{ token: string; userInfo: UserInfo }>('/api/login', params);      // 1. 存储 Token 到 Cookie      setToken(res.token);      // 2. 存储用户信息到 LocalStorage      setUserInfo(res.userInfo);      // 3. 更新 Pinia 状态(响应式)      userInfo.value = res.userInfo;      return true;    } catch (error) {      console.error('登录失败', error);      return false;    }  };  // 登出操作  const logout = () => {    // 1. 清空本地存储    clearLoginState();    // 2. 清空 Pinia 状态    userInfo.value = null;    // 3. 可选:调用后端登出接口(销毁服务端 Session/Token)    request.post('/api/logout');  };  return {    userInfo,    isLogin,    initUserState,    login,    logout  };});

步骤 4:全局初始化登录态(App.vue)

页面刷新后恢复登录状态,保证状态不丢失:

vue

[xss_clean]import { onMounted } from 'vue';import { useUserStore } from '@/stores/user';const userStore = useUserStore();// 页面挂载时初始化登录态onMounted(() => {  userStore.initUserState();});[xss_clean]

步骤 5:登录组件使用示例(Login.vue)

vue

[xss_clean]import { ref } from 'vue';import { useUserStore } from '@/stores/user';import { useRouter } from 'vue-router';import type { LoginParams } from '@/types/user';const userStore = useUserStore();const router = useRouter();// 表单数据(TS 类型约束)const form = ref({  username: '',  password: ''});// 登录按钮点击事件const handleLogin = async () => {  const success = await userStore.login(form.value);  if (success) {    // 登录成功跳转到首页    router.push('/home');  }};[xss_clean]

步骤 6:请求拦截器(自动携带 Token)

新建 src/utils/request.ts,封装 Axios 并自动携带 Token,实现登录态验证:

typescript

运行

import axios from 'axios';import { getToken } from '@/utils/storage';import { useUserStore } from '@/stores/user';// 创建 Axios 实例const request = axios.create({  baseURL: import.meta.env.VITE_API_BASE_URL,  timeout: 5000});// 请求拦截器:添加 Tokenrequest.interceptors.request.use(  (config) => {    const token = getToken();    if (token) {      // 把 Token 放到请求头(或依赖 Cookie 自动携带)      config.headers.Authorization = `Bearer ${token}`;    }    return config;  },  (error) => Promise.reject(error));// 响应拦截器:处理 Token 过期request.interceptors.response.use(  (response) => response.data,  (error) => {    // 401 表示 Token 过期/未登录    if (error.response?.status === 401) {      const userStore = useUserStore();      userStore.logout(); // 清空登录态      // 跳转到登录页      _window.location.href = '/login';    }    return Promise.reject(error);  });export { request };

三、关键注意事项(TS + 安全)

  1. 类型约束全覆盖:所有存储 / 请求 / 状态都要定义 TS 接口,避免 any 类型,提升代码可维护性;
  2. 敏感信息不裸存: Token/Session ID 优先存在 Cookie(开启 HttpOnly/Secure); LocalStorage 只存非敏感用户信息(如用户名、角色),不存密码 / 完整 Token;
  3. 登录态双重验证:前端存储的只是「标识」,最终需通过后端接口验证(如每次请求携带 Token,后端查 Redis 校验);
  4. Token 过期处理:响应拦截器捕获 401 状态码,自动登出并跳转登录页;
  5. 登出需清全量:既要清空前端存储(Cookie/LocalStorage),也要调用后端接口销毁服务端登录态。

总结(核心要点)

  1. 存储组合:Cookie 存 Token/Session ID + LocalStorage 存用户信息 + Pinia 做响应式状态管理;
  2. TS 核心:通过接口定义 UserInfo/LoginParams,保证所有操作的类型安全;
  3. 安全原则:敏感信息用 Cookie(带安全配置),前端只存标识,最终验证依赖后端。

如果需要适配 JWT 场景(如 Token 存在 LocalStorage + 刷新 Token 逻辑),或补充路由守卫(未登录拦截)的代码,可以告诉我,我会针对性补充。

vue typescript,如何验证cookie过期,是在后端服务器,还是前端服务器?

在 Vue + TypeScript 项目中,Cookie 过期的核心验证逻辑必须在后端服务器,前端仅做「辅助判断」和「友好处理」—— 这是保障登录态安全的核心原则。下面分「核心结论」「前后端分工」「TS 实战代码」三部分讲清楚,新手能直接落地。


一、核心结论

表格

职责

后端服务器(必做)

前端(Vue + TS,辅助)

Cookie 过期验证

✅ 校验 Token/Session ID 是否有效、是否过期

❌ 不做核心验证(前端可被篡改)

过期结果返回

✅ 返回明确状态码(如 401 Unauthorized)

✅ 接收状态码,执行登出 / 跳转等操作

过期时间管理

✅ 控制 Cookie 的 Expires/Max-Age 配置

✅ 可选:读取 Cookie 过期时间做前端提示

简单说:后端是「裁判」,前端是「执行者」 —— 前端永远不能自己判断「登录是否有效」,必须以后端的校验结果为准。


二、前后端分工详解(结合 TS 实战)

1. 后端服务器:Cookie 过期的核心验证(关键)

Cookie 本质是「客户端存储的标识」(如 Session ID/Token),真正的过期逻辑在后端:

  • Session 模式:后端 Redis 中存储 Session ID 对应的过期时间,每次请求时校验: 如果 Redis 中 Session 不存在 / 已过期 → 返回 401 状态码; 如果有效 → 正常返回数据。
  • JWT 模式:后端解析 Token 时校验签名和过期时间(exp 字段),过期则返回 401。
  • Cookie 配置:后端设置 Cookie 时,会指定 Expires/Max-Age(如 2 小时),浏览器会自动在过期后删除 Cookie,但前端删除不代表后端状态失效(仍需后端校验)。

2. 前端(Vue + TS):处理过期结果(核心代码)

前端不直接验证 Cookie 是否过期,而是通过「拦截后端响应」处理过期场景,以下是完整 TS 实现:

步骤 1:封装 Axios 响应拦截器(捕获 401 过期状态)

新建 src/utils/request.ts,这是前端处理 Cookie 过期的核心入口:

typescript

运行

import axios from 'axios';import Cookies from 'js-cookie';import { useUserStore } from '@/stores/user';import { ElMessage } from 'element-plus'; // 可选:UI 库提示// 创建 Axios 实例const request = axios.create({  baseURL: import.meta.env.VITE_API_BASE_URL,  timeout: 5000,  withCredentials: true // 关键:允许携带 Cookie(跨域时必须)});// 响应拦截器:处理后端返回的过期状态request.interceptors.response.use(  (response) => {    // 后端返回正常数据 → 直接返回    return response.data;  },  async (error) => {    const { response } = error;    // 核心:捕获后端返回的 401 状态码(表示 Token/Session 过期/无效)    if (response && response.status === 401) {      const userStore = useUserStore();      // 1. 清空前端所有登录态(Cookie/LocalStorage/Pinia)      userStore.logout();      // 2. 友好提示      ElMessage.error('登录状态已过期,请重新登录');      // 3. 跳转登录页(避免死循环:排除登录页本身)      if _(window.location.pathname !== '/login') {        _window.location.href = '/login';      }    }    // 其他错误(如 500)正常抛出    return Promise.reject(error);  });export { request };

步骤 2:Pinia 中封装 logout 方法(清空前端 Cookie)

在之前的 src/stores/user.ts 中完善 logout 逻辑,确保前端 Cookie 被清理:

typescript

运行

import { defineStore } from 'pinia';import { ref, computed } from 'vue';import Cookies from 'js-cookie';import { request } from '@/utils/request';import type { UserInfo, LoginParams } from '@/types/user';export const useUserStore = defineStore('user', () => {  // ... 其他状态(省略)  // 登出:清空前端 Cookie + 通知后端  const logout = async () => {    try {      // 1. 调用后端登出接口(让后端销毁 Session/Token)      await request.post('/api/logout');    } finally {      // 2. 清空前端 Cookie(关键:不管后端是否成功,前端都要清)      Cookies.remove('USER_TOKEN'); // 对应后端设置的 Cookie 键名      // 3. 清空 LocalStorage 和 Pinia 状态      localStorage.removeItem('USER_INFO');      userInfo.value = null;    }  };  return { userInfo, isLogin, login, logout };});

步骤 3:可选:前端读取 Cookie 过期时间(仅做提示)

如果需要在前端「提前提示」Cookie 即将过期(非核心验证),可通过 TS 解析 Cookie 的过期时间:

typescript

运行

// src/utils/cookie.tsimport Cookies from 'js-cookie';/** * 读取 Cookie 的过期时间(仅做前端提示,不做验证) * @param key Cookie 键名 * @returns 过期时间(Date)或 null */export const getCookieExpires = (key: string): Date | null => {  // 注意:js-cookie 无法直接获取过期时间,需解析 [xss_clean]  const cookies = [xss_clean].split('; ');  for (const cookie of cookies) {    const [name, value] = cookie.split('=');    if (name === key) {      // 后端设置 Cookie 时需返回 Expires 信息(或前端自己记录)      // 示例:假设后端返回的 Cookie 包含过期时间,或前端存储了过期时间      const expiresStr = Cookies.get(`${key}_expires`); // 需后端配合      if (expiresStr) {        return new Date(expiresStr);      }      return null;    }  }  return null;};/** * 检查 Cookie 是否即将过期(仅提示,不做验证) * @param key Cookie 键名 * @param minutes 提前提示的分钟数 * @returns 是否即将过期 */export const isCookieAboutToExpire = (key: string, minutes = 10): boolean => {  const expires = getCookieExpires(key);  if (!expires) return false;  // 计算剩余时间(毫秒)  const remaining = expires.getTime() - Date.now();  // 剩余时间 < 设定的提前时间(分钟转毫秒)→ 提示  return remaining < minutes * 60 * 1000;};

在组件中使用(仅做提示,不影响登录态):

vue

[xss_clean]import { onMounted } from 'vue';import { isCookieAboutToExpire } from '@/utils/cookie';import { ElMessage } from 'element-plus';onMounted(() => {  // 检查 Token Cookie 是否即将过期(提前 10 分钟提示)  if (isCookieAboutToExpire('USER_TOKEN', 10)) {    ElMessage.warning('登录状态即将过期,请刷新页面或重新登录');  }});[xss_clean]

3. 后端关键配置(补充,帮助你理解)

以 Node.js/Express 为例,后端设置 Cookie 并校验过期:

javascript

前端存储(vue typescript 登录状态,如何存储)

运行

// 1. 设置 Cookie 时指定过期时间res.cookie('USER_TOKEN', sessionId, {  httpOnly: true, // 防 XSS(前端无法通过 JS 读取)  secure: process.env.NODE_ENV === 'production', // 仅 HTTPS 传输  maxAge: 2 * 60 * 60 * 1000, // 2 小时过期(毫秒)  path: '/'});// 2. 校验 Cookie 过期(核心)app.use((req, res, next) => {  const sessionId = req.cookies.USER_TOKEN;  // 从 Redis 中查 Session 是否存在/过期  redis.get(`session:${sessionId}`, (err, data) => {    if (!data) {      // Session 过期/无效 → 返回 401      return res.status(401).json({ msg: '登录状态已过期' });    }    next(); // 有效则继续  });});

三、关键注意事项(Vue + TS)

  1. 前端绝不信任本地判断:即使前端读取到 Cookie 没过期,也必须以后端响应为准(比如用户手动修改了本地时间,或 Cookie 被篡改);
  2. withCredentials 必须开启:跨域请求时,Axios 需设置 withCredentials: true,否则浏览器不会携带 Cookie 到后端;
  3. 401 处理要防死循环:跳转登录页时,要排除登录页本身(否则会无限跳转);
  4. HttpOnly Cookie:后端设置 httpOnly: true 后,前端无法通过 JS 读取 Cookie —— 这是安全的,前端只需通过后端状态码判断即可。

总结(核心要点)

  1. 验证主体:Cookie 过期的核心验证在后端服务器(校验 Session/Token 有效性);
  2. 前端职责:通过 Axios 拦截器捕获后端 401 状态码,执行「清空登录态 + 跳转登录页」;
  3. 安全原则:前端仅做「辅助提示」(如即将过期),绝不做「是否登录有效」的核心判断。

文章版权声明:除非注明,否则均为边学边练网络文章,版权归原作者所有