
客户端存储与服务端存储
目录
[1.1 Cookie](#1.1 Cookie)
[1.2 sessionStorage(标签页隔离临时存储)](#1.2 sessionStorage(标签页隔离临时存储))
[1.3 localStorage(同源全局持久化 Web Storage)](#1.3 localStorage(同源全局持久化 Web Storage))
[1.4 补充:IndexedDB](#1.4 补充:IndexedDB)
[客户端存储完整对比表(含 IndexedDB)](#客户端存储完整对比表(含 IndexedDB))
[二、服务端存储核心架构:两种范式(Session 有状态 / JWT 双 Token 无状态)](#二、服务端存储核心架构:两种范式(Session 有状态 / JWT 双 Token 无状态))
[2.1 服务端 Session(有状态会话)](#2.1 服务端 Session(有状态会话))
[2.2 JWT 双 Token(无状态,行业主流方案)](#2.2 JWT 双 Token(无状态,行业主流方案))
[三、纯 Vue/React SPA 工程化落地(客户端渲染)](#三、纯 Vue/React SPA 工程化落地(客户端渲染))
[3.1 最优存储架构(生产标准规范)](#3.1 最优存储架构(生产标准规范))
[3.2 生产级 Axios 无感刷新(解决并发 401、重复刷新、死循环问题)](#3.2 生产级 Axios 无感刷新(解决并发 401、重复刷新、死循环问题))
[3.3 核心避坑要点](#3.3 核心避坑要点)
[四、Nuxt 3(Vue SSR)同构存储落地](#四、Nuxt 3(Vue SSR)同构存储落地)
[4.1 SSR 核心矛盾与解决方案](#4.1 SSR 核心矛盾与解决方案)
[4.2 Nuxt 官方同构核心 API](#4.2 Nuxt 官方同构核心 API)
[useCookie:跨端响应式 Cookie(首选鉴权/全局配置)](#useCookie:跨端响应式 Cookie(首选鉴权/全局配置))
[useStorage:同构 KV 存储(完美替代 localStorage)](#useStorage:同构 KV 存储(完美替代 localStorage))
[4.3 Nuxt2 兼容方案](#4.3 Nuxt2 兼容方案)
[五、Next.js(React SSR)存储工程化](#五、Next.js(React SSR)存储工程化)
[5.1 App Router 核心 Cookie API](#5.1 App Router 核心 Cookie API)
[5.2 生产级会话方案:iron-session](#5.2 生产级会话方案:iron-session)
[5.3 客户端安全 useLocalStorage Hook(彻底解决水合不匹配)](#5.3 客户端安全 useLocalStorage Hook(彻底解决水合不匹配))
[6.1 存储选型决策表(可直接落地执行)](#6.1 存储选型决策表(可直接落地执行))
[6.2 安全硬性红线(生产绝对不可突破)](#6.2 安全硬性红线(生产绝对不可突破))
[6.3 SSR 高频踩坑修复方案](#6.3 SSR 高频踩坑修复方案)
[1. 存储分工标准化](#1. 存储分工标准化)
[2. 认证架构统一化](#2. 认证架构统一化)
[3. SSR 同构核心逻辑](#3. SSR 同构核心逻辑)
[4. 安全核心思想](#4. 安全核心思想)
一、客户端存储基础体系
前端原生客户端存储分为两大核心阵营:
HTTP Cookie ( HTTP 层存储) 、Web Storage ( DOM 层: localStorage / sessionStorage ),
同时补充大容量离线存储 IndexedDB及现代多标签通信方案,构建完整、覆盖全业务场景的前端存储矩阵。
1.1 Cookie
(HTTP 绑定型存储,唯一可自动上行至服务端的客户端存储)
完整概念与底层属性扩充
Cookie 由服务端 Set-Cookie 响应头写入浏览器,遵循同源 + Path + Domain 三重作用域限制 ,每次同源 HTTP 请求会自动挂载在 Cookie 请求头中上传服务端。
业界标准容量规范:
单个 Cookie 最大 4096 字节( 4KB ),同域名下所有 Cookie 总大小通常不超过 4KB(不同浏览器实现略有宽松)。
浏览器原生自动管理会话/持久化生命周期。
核心安全属性详解(生产环境必配)
|------------------------------|-------------------------------------------|-----------------------------------------------|
| 属性 | 作用 | 硬性约束 |
| HttpOnly | 禁止 document.cookie JS 读取,彻底阻断 XSS 凭证窃取攻击 | 仅服务端 Set-Cookie 可设置,前端 JS 无法写入、读取该属性 Cookie |
| Secure | 仅 HTTPS 加密协议下携带 Cookie,HTTP 环境自动失效 | 测试环境可临时关闭,生产环境强制开启 |
| SameSite=Lax/Strict/None | 防御 CSRF 跨站请求伪造攻击 | SameSite=None 必须搭配 Secure 属性,否则浏览器直接丢弃 Cookie |
| Domain | 实现主域、子域 Cookie 共享(如 .example.com 匹配所有子域名) | 不可设置为当前域名的上级无关域名,否则失效 |
| Path | 限定仅指定路由接口携带 Cookie,缩小请求携带范围 | 可精准隔离刷新凭证、业务凭证,减少无效带宽与攻击面 |
生命周期细分
- 会话 Cookie (无 Expires / Max-Age ) :依附浏览器进程,完全关闭浏览器即销毁,重启浏览器登录态失效;
- 持久 Cookie (配置 Max-Age / Expires ):持久化写入本地磁盘,标签页关闭、浏览器重启均保留,到达过期时间后浏览器自动删除。
落地场景补充
- 跨子域单点登录 :配置 Domain=.example.com,实现主站、后台、文件子域共享统一登录态;
- 凭证路径隔离 :Refresh Token 限定 Path=/api/auth/refresh,仅刷新接口可携带,业务接口不传递刷新凭证,极致缩小攻击面;
- CSRF 校验载体:JS 可读 Cookie 存储 CSRF 令牌,请求头同步携带完成二次校验。
工具函数优化(完整增删改、支持域名/过期/安全属性)
javascript
function setCookie(name, value, days = 7, path = '/', domain = '', secure = false, sameSite = 'Lax') {
let str = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
if (days) str += `; max-age=${days * 24 * 60 * 60}`;
if (path) str += `; path=${path}`;
if (domain) str += `; domain=${domain}`;
if (secure) str += `; secure`;
if (sameSite) str += `; SameSite=${sameSite}`;
document.cookie = str;
}
function getCookie(name) {
const arr = document.cookie.split('; ');
for (const item of arr) {
const [k, v] = item.split('=');
if (decodeURIComponent(k) === name) return decodeURIComponent(v);
}
return null;
}
function removeCookie(name, path = '/', domain = '') {
setCookie(name, '', -1, path, domain);
}
1.2 sessionStorage(标签页隔离临时存储)
底层机制补充
- 挂载页面独立会话上下文,同源不同标签页完全隔离 ,window.open 新开页面属于全新会话,无法继承父页面数据;SPA 内部路由跳转属于同一会话,数据持续保留;
- 仅支持字符串存储,对象、数组必须通过 JSON.stringify 序列化,超大体积数据易出现写入失败;
- 无跨页 storage 事件:仅 localStorage 变更触发跨标签事件,sessionStorage 变更仅当前页面可见。
典型落地场景
- 多步骤表单草稿存储(路由切换保留数据、关闭标签自动清空,无垃圾数据残留);
- 一次性弹窗状态、临时路由中转参数存储;
- 短期 Access Token 兜底存储(关闭页面自动登出,提升安全性)。
1.3 localStorage(同源全局持久化 Web Storage)
底层机制补充
- 数据持久化写入磁盘,手动清除浏览器缓存、无痕/隐私模式下会被强制清空;
- 同源所有标签页全局共享,单个标签页修改数据,其他同源标签页触发 storage 事件,可用于多标签同步登出、主题切换;
- 原生同步阻塞 API ,大体积数据密集写入会阻塞主线程,禁止存储 1MB 以上业务数据;
- 现代化多标签通信优化:复杂场景优先使用 BroadcastChannel API,替代传统 storage 事件,无需全量序列化数据,通信性能更优、扩展性更强。
风险强化提示
所有 Web Storage(localStorage/sessionStorage)均可被 XSS 漏洞直接读取篡改,生产环境严禁存储 RefreshToken 、核心鉴权凭证、用户隐私数据。
1.4 补充:IndexedDB
- 容量规格 :现代浏览器采用动态配额机制,可用空间可达数百 MB 甚至数 GB ,配额与设备磁盘剩余空间挂钩;支持二进制、结构化数据、索引查询;
- 适用场景 :PWA 离线资源缓存、超大表单草稿、图片静态缓存、前端大数据表格本地持久化;
- 工程短板 :原生异步 API 语法繁琐、学习成本高,项目中统一使用 Dexie.js 封装简化开发;
- 核心定位:仅用于纯业务数据缓存,绝不存储身份鉴权凭证。
客户端存储完整对比表(含 IndexedDB)
|----------------|-------------------------|-------------------------------|--------------------|----------------------|
| 特性 | Cookie | localStorage | sessionStorage | IndexedDB |
| 最大容量 | 单条~4KB,同域总容量约4KB | ~5MB | ~5MB | 动态配额(数百 MB~GB) |
| 生命周期 | 会话临时 / 自定义持久过期 | 永久(手动清理失效) | 标签页关闭立即销毁 | 永久持久化 |
| 同源作用域 | 支持 Domain 跨子域、Path 路由限定 | 同源全部标签页共享 | 仅当前标签会话隔离 | 同源全局共享 |
| 自动上传服务端 | ✅ 同源请求自动携带 | ❌ 需手动塞入请求头 | ❌ 需手动塞入请求头 | ❌ 完全手动控制 |
| JS 可访问 | HttpOnly 模式禁止 JS 读取 | ✅ 完全可读可写 | ✅ 完全可读可写 | ✅ 完全可读可写 |
| 主线程阻塞 | 无阻塞 | 同步 API,大写入易阻塞 | 同步 API,大写入易阻塞 | 异步无阻塞 |
| 跨页同步能力 | 无原生事件 | storage 事件 / BroadcastChannel | 无跨页同步 | 自定义广播实现 |
| 安全等级 | 高(多层安全属性防护) | 低(XSS 高危) | 低(XSS 高危) | 低(XSS 高危) |
| 典型业务用途 | 登录凭证、CSRF 校验、跨域偏好配置 | 主题、语言、全局静态配置缓存 | 临时表单、一次性会话状态 | 离线大缓存、PWA 资源、大数据本地存储 |
二、服务端存储核心架构:两种范式(Session 有状态 / JWT 双 Token 无状态)
客户端所有存储仅作为数据 / 凭证载体,核心鉴权逻辑、权限校验、用户可信数据全部由服务端管控,前端不做任何信任兜底。
行业主流分为两种会话架构:
2.1 服务端 Session(有状态会话)
用户登录成功后,服务端生成唯一 SessionId,通过 HttpOnly Cookie 下发至浏览器;
会话核心数据(用户权限、账号信息)存储在服务端 Redis(分布式项目)、服务器内存(单体项目)或数据库中。
客户端请求自动携带 SessionId,服务端查询会话数据完成身份校验。
优势 :支持主动踢下线、敏感数据不落地前端、CSRF 防护成本低;
劣势:分布式部署需 Redis 实现会话中心化,无法完全无状态扩容。
2.2 JWT 双 Token(无状态,行业主流方案)
标准化 AccessToken (短期鉴权) + RefreshToken (长期刷新) 双凭证架构,完美平衡安全性与用户体验:
- AccessToken :有效期 1~2 小时,仅存储在前端内存(Pinia/Redux/Vuex),用于日常接口鉴权,页面刷新自动清空;
- RefreshToken :有效期 7~30 天,强制 HttpOnly Cookie 存储 ,限定路径仅用于 /api/auth/refresh 刷新接口,前端 JS 无法读取。
优势 :服务端无状态,无需存储会话数据,支持无限水平扩容;
劣势:无法主动失效 Token,依靠短期 AccessToken 降低安全风险。
核心协同铁律
所有可信业务数据、权限信息、用户核心隐私数据,禁止前端存储信任。
前端仅负责存储凭证、配置、缓存,所有接口权限、身份校验必须由服务端二次校验。
三、纯 Vue/React SPA 工程化落地(客户端渲染)
3.1 最优存储架构(生产标准规范)
- RefreshToken :HttpOnly + Secure + SameSite=Lax + 限定 Path 的 Cookie;
- AccessToken :全局状态库内存存储,页面刷新清空;
- 主题 / 语言 /UI 偏好 :localStorage 持久化;
- 多步骤表单草稿:sessionStorage 临时存储。
3.2 生产级 Axios 无感刷新(解决并发 401、重复刷新、死循环问题)
核心方案:刷新锁 + 请求队列,多个接口同时 401 时仅执行一次 Token 刷新,其余请求排队重试,杜绝重复刷新与死循环。
全局开启 withCredentials: true 保证跨域场景 Cookie 正常上行。
javascript
import axios from 'axios';
import { useAuthStore } from '@/stores/auth';
const service = axios.create({
baseURL: '/api',
withCredentials: true, // 全局开启Cookie携带,跨域必备
});
// 刷新锁与请求队列
let isRefreshing = false;
let requestQueue = [];
// 请求拦截器:注入内存AccessToken
service.interceptors.request.use(config => {
const token = useAuthStore().accessToken;
if (token) config.headers.Authorization = `Bearer ${token}`;
return config;
});
// 响应拦截器:统一处理Token过期401
service.interceptors.response.use(
res => res.data,
async error => {
const originalReq = error.config;
// 拦截401、未重试、非刷新接口,杜绝死循环
if (
error.response?.status === 401 &&
!originalReq._retry &&
originalReq.url !== '/api/auth/refresh'
) {
// 正在刷新,请求入队等待
if (isRefreshing) {
return new Promise(resolve => {
requestQueue.push(token => {
originalReq.headers.Authorization = `Bearer ${token}`;
resolve(service(originalReq));
});
});
}
// 开启刷新流程,锁定状态
isRefreshing = true;
originalReq._retry = true;
try {
// RefreshToken自动从Cookie携带,无需前端传参
const { data } = await axios.post('/api/auth/refresh');
// 更新全局内存Token
useAuthStore().setToken(data.accessToken);
// 批量重试队列请求
requestQueue.forEach(cb => cb(data.accessToken));
requestQueue = [];
// 重试当前失败请求
originalReq.headers.Authorization = `Bearer ${data.accessToken}`;
return service(originalReq);
} catch (refreshErr) {
// 刷新失败=登录态过期,清空状态跳转登录页
useAuthStore().logout();
requestQueue = [];
window.location.href = '/login';
return Promise.reject(refreshErr);
} finally {
// 释放刷新锁
isRefreshing = false;
}
}
return Promise.reject(error);
}
);
3.3 核心避坑要点
- 严禁将 AccessToken 长期存储在 localStorage,规避 XSS 窃取风险;
- 全局统一开启 withCredentials: true,保证跨域场景 Cookie 正常上行;
- 全站配置 CSP 内容安全策略,禁止非可信脚本注入,加固 Web Storage 安全防线。
四、Nuxt 3(Vue SSR)同构存储落地
4.1 SSR 核心矛盾与解决方案
服务端 Node.js 环境无 window/localStorage/sessionStorage 对象,直接调用会直接报错;
SSR 核心原理为:
服务端脱水( Dehydrate ):服务端渲染阶段获取状态并注入 HTML payload,
客户端注水( Hydrate ):客户端挂载后激活状态,解决首屏闪烁、状态不匹配问题。
Nuxt3 内置同构 API 完美抹平前后端环境差异。
4.2 Nuxt 官方同构核心 API
useCookie:跨端响应式 Cookie(首选鉴权/全局配置)
服务端自动读取请求 Cookie、写入响应头 Set-Cookie;
客户端自动操作 document.cookie,全程响应式,无需环境判断。
javascript
const theme = useCookie<'light'|'dark'>('site_theme', {
maxAge: 60 * 60 * 24 * 7,
path: '/',
sameSite: 'lax'
});
useStorage:同构 KV 存储(完美替代 localStorage)
底层自动适配环境:服务端基于内存/文件缓存,客户端自动映射为 localStorage,支持过期、前缀隔离,彻底规避服务端报错。
javascript
const appCache = useStorage('app_config', { layout: 'default' });
appCache.value.layout = 'compact'; // 客户端自动落地本地持久化
用户登录态同构最佳实践(无水合闪烁)
利用 useFetch 服务端预请求,解析 Cookie 完成鉴权,通过 Nuxt payload 脱水下发,客户端直接复用状态,首屏无登录态闪烁。
javascript
export const useUserSession = () => {
const { data: user } = useFetch('/api/auth/session', {
key: 'user_session',
default: () => null,
server: true, // 强制服务端执行
});
return { user };
};
服务端接口读取 Cookie 校验会话,实现纯服务端鉴权,前端无感知:
javascript
// server/api/auth/session.get.ts
export default defineEventHandler(async (event) => {
const sessionId = getCookie(event, 'session_id')
if (!sessionId) return null
// 服务端Redis校验会话有效性
return await verifySession(sessionId)
})
4.3 Nuxt2 兼容方案
通过 nuxtServerInit 在服务端初始化 Vuex,从 req.headers.cookie 提取凭证,完成首屏状态注水,适配旧项目架构。
五、Next.js(React SSR)存储工程化
Next.js App Router 严格区分服务端组件(RSC)与客户端组件,服务端无 DOM API,需使用框架内置专属接口操作存储。
5.1 App Router 核心 Cookie API
服务端组件通过 next/headers 读取 Cookie,Server Action 实现 Cookie 写入,全程服务端完成,安全无风险。
javascript
// 服务端组件读取Cookie(无'use client')
import { cookies } from 'next/headers';
export default function Home() {
const cookieStore = cookies();
const theme = cookieStore.get('site_theme')?.value || 'light';
return <body className={theme}></body>;
}
// Server Action 服务端修改Cookie
async function setTheme(formData: FormData) {
'use server';
const theme = formData.get('theme') as string;
cookies().set('site_theme', theme, { maxAge: 60*60*24*7, path: '/' });
}
5.2 生产级会话方案:iron-session
Next 生态标准会话方案,无需服务端 Redis 存储,将会话信息 AES 加密后存入 HttpOnly Cookie,兼顾无状态与安全性,适配所有渲染模式。
javascript
import { getIronSession } from 'iron-session';
// Pages Router 登录写入会话
export default async function handler(req, res) {
const session = await getIronSession(req, res, {
password: process.env.SESSION_SECRET,
cookieName: 'iron_session',
cookieOptions: { httpOnly: true, secure: process.env.NODE_ENV === 'production' }
});
session.user = { id: 1, name: 'admin' };
await session.save();
res.json({ success: true });
}
5.3 客户端安全 useLocalStorage Hook(彻底解决水合不匹配)
基于 useSyncExternalStore 封装,服务端渲染使用默认值,客户端挂载后读取本地存储,杜绝 SSR 水合报错、状态闪烁。
TypeScript
'use client';
import { useSyncExternalStore, useCallback } from 'react';
function useLocalStorage<T>(key: string, defaultValue: T) {
const subscribe = useCallback((cb: () => void) => {
window.addEventListener('storage', cb);
return () => window.removeEventListener('storage', cb);
}, [key]);
const getSnapshot = useCallback(() => {
try {
const v = localStorage.getItem(key);
return v ? JSON.parse(v) : defaultValue;
} catch {
return defaultValue;
}
}, [key, defaultValue]);
const setState = useCallback((value: T) => {
localStorage.setItem(key, JSON.stringify(value));
window.dispatchEvent(new StorageEvent('storage', { key }));
}, [key]);
const state = useSyncExternalStore(subscribe, getSnapshot, () => defaultValue);
return [state, setState] as const;
}
六、跨框架最佳实践、安全红线、高频踩坑总结
6.1 存储选型决策表(可直接落地执行)
|-------------------------------|-----------------------------------------|-------------------------------|
| 数据类型 | 推荐存储方案 | 禁止存储方案 |
| RefreshToken / 核心会话凭证 | HttpOnly Cookie(限定Path、Secure、SameSite) | localStorage / sessionStorage |
| AccessToken 短期鉴权 | 内存状态库(Pinia / Redux / Vuex) | 所有持久化 Storage |
| 主题、语言、 UI 全局偏好 | Cookie(SSR首屏无闪烁)/ localStorage(SPA) | sessionStorage(会话丢失) |
| 多步骤表单草稿 | sessionStorage | localStorage(残留垃圾数据) |
| 离线大缓存、 PWA 资源 | IndexedDB(配套 Dexie.js) | Web Storage(容量不足) |
| 一次性临时路由参数 | sessionStorage | Cookie(无效上行、冗余带宽) |
6.2 安全硬性红线(生产绝对不可突破)
- HttpOnly 鉴权 Cookie 仅允许服务端 Set-Cookie 下发 ,前端 JS 禁止写入、修改核心鉴权 Cookie;
- SameSite=None 必须强制搭配 Secure 属性,且全程 HTTPS 协议,否则浏览器直接丢弃 Cookie;
- 登录、刷新鉴权接口强制开启 withCredentials: true,保证跨域场景 Cookie 正常上行;
- 全站开启 CSP 内容安全策略,禁止 innerHTML 渲染非可信用户内容,杜绝 XSS 攻击;
- CSRF 双重防御:Cookie 配置 SameSite=Lax + 关键接口追加 CSRF Token 二次校验。
6.3 SSR 高频踩坑修复方案
- 服务端调用 localStorage/sessionStorage 报错 :使用框架同构 API(useCookie/useStorage)或 process.client / 客户端环境判断;
- 首屏主题 / 登录态闪烁(水合不匹配) :全局偏好、用户状态存入 Cookie,服务端直接读取渲染,不依赖客户端 Storage;
- SSR 环境 Token 刷新失效:刷新逻辑放在服务端中间件/接口,通过解析 Cookie 完成鉴权刷新,不依赖客户端内存 Token。
七、最终总结
1. 存储分工标准化
Cookie 承担 HTTP 自动上行、SSR 同构配置、安全凭证存储;Web Storage 承担纯客户端临时/持久缓存;IndexedDB 承接大容量离线业务场景,三者各司其职、无场景冲突
2. 认证架构统一化
双 Token + HttpOnly Refresh Cookie 是 SPA、SSR 通用的生产级安全方案,平衡安全性、用户体验与服务端扩容能力
3. SSR 同构核心逻辑
依托 Nuxt/Next 官方抽象 API 抹平前后端环境差异,通过服务端脱水 、客户端注水机制,彻底解决首屏闪烁、状态不匹配、服务端报错问题
4. 安全核心思想
所有前端存储仅作为数据载体,核心鉴权、权限校验、会话管控全部上移至服务端,从根源杜绝前端篡改、凭证泄露、越权访问等安全风险。