Cookie 存储 (Cookie Store)
@pt/utils/modules/storage 提供了基于 js-cookie 的存储封装,支持加密存储和灵活的 cookie 选项。
源码位置: jsCookie.ts
CookieStore 类
CookieStore 类提供了对浏览器 Cookie 的增强封装,支持数据加密、域名自动检测、现代浏览器的安全策略,以及内嵌(iframe)场景下自动切换为 localStorage 存储。
基本用法
import { cookieStore, setSetting } from '@pt/utils/modules/storage';
// 设置普通数据
cookieStore.set('user', { name: 'John' });
// 设置加密数据(第三个参数为true表示加密)
cookieStore.set('token', 'secret123', true);
// 获取数据
const user = cookieStore.get('user');
// 移除数据
cookieStore.remove('user');
// 清除所有数据
cookieStore.clear();2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
关于命名
cookieStore 是 storage 的重命名导出(storage as cookieStore),两者指向同一个实例。原名 storage 仍然保留导出,已有代码无需修改:
// ✅ 推荐:语义更清晰
import { cookieStore } from '@pt/utils/modules/storage';
// ✅ 兼容:原有写法仍然有效
import { storage } from '@pt/utils/modules/storage';
// cookieStore === storage,完全等价2
3
4
5
6
7
主要特性
1. 数据加密
支持敏感数据加密存储,保护用户隐私和安全信息。
// 存储加密的认证令牌
cookieStore.set('authToken', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...', true);
// 存储加密的用户凭证
cookieStore.set('credentials', {
username: 'admin',
sessionId: 'abc123'
}, true);
// 获取时自动解密
const token = cookieStore.get('authToken');
const credentials = cookieStore.get('credentials');2
3
4
5
6
7
8
9
10
11
12
2. 域名自动检测
智能处理 cookie 域,自动适配不同环境。
3. SameSite 策略
支持现代浏览器的 cookie 安全策略配置。
4. 批量操作
支持批量获取和设置数据。
5. 内嵌模式 (Embedded / iframe)
当应用以 iframe 方式嵌入第三方页面时,部分浏览器会限制第三方 Cookie 的读写。CookieStore 提供了 isEmbedded 选项,开启后自动使用 localStorage 替代 Cookie 进行数据存储,同时支持过期时间管理。
import { CookieStore } from '@pt/utils/modules/storage';
// 创建内嵌模式的存储实例
const embeddedStore = new CookieStore({
storageKey: 'my_app_storage',
isEmbedded: true,
});
// 使用方式与普通模式完全一致
embeddedStore.set('user', { name: 'John' });
embeddedStore.set('token', 'secret123', true); // 同样支持加密
const user = embeddedStore.get('user');
embeddedStore.remove('user');
embeddedStore.clear(); // 清除 localStorage 中对应的数据2
3
4
5
6
7
8
9
10
11
12
13
14
15
内嵌模式的过期时间
内嵌模式下,通过在 localStorage 数据中维护 __expires_at 时间戳来模拟 Cookie 的过期机制:
// 设置带过期时间的数据(单位:天)
embeddedStore.set('expires_time', 7); // 设置 7 天后过期
embeddedStore.set('token', 'my_token', true);
// 读取时自动检查过期,过期数据会被自动清除
const token = embeddedStore.get('token');2
3
4
5
6
何时使用内嵌模式?
- 应用被 iframe 嵌入到其他域名的页面中
- 浏览器阻止了第三方 Cookie(如 Safari 默认策略、Chrome 的隐身模式等)
- 需要在跨域 iframe 中持久化用户状态
关于 iframe 跨域 Cookie 限制的详细说明和解决方案,请参阅:iframe 跨域指南
iframe 场景下的 Cookie 限制说明
SameSite=Lax 的准确含义(来自 MDN 和 PortSwigger):
Lax 表示浏览器在跨站请求中仍会发送 cookie,但必须同时满足两个条件:请求使用 GET 方法,且是用户主动触发的顶级导航(如点击链接)。因此 cookie 不会包含在 iframe、脚本发起的后台请求、图片资源请求中。
但对 iframe 场景的影响是一致的:
如果你的站点以 iframe 形式嵌入到不同域名的站点中,你的站点将不会收到任何 Lax(或 Strict)cookie。
使用 <iframe> 的应用可能会遇到 sameSite=Lax 或 sameSite=Strict 的问题,因为 <iframe> 被视为跨站场景。要允许跨站 cookie,必须使用 SameSite=None,但同时必须标记为 Secure。
核心问题: iframe 跨站场景下 Lax cookie 无法写入;而在 HTTP 环境下,SameSite=None 又必须配合 Secure(HTTPS)才能生效。
因此,在 iframe 跨域且无法保证 HTTPS 的情况下,降级使用 localStorage(即开启 isEmbedded: true)仍然是最可行的解决方案。
API 参考
构造函数
new CookieStore(options?: {
storageKey?: string;
options?: Partial<Option>;
isEmbedded?: boolean;
})2
3
4
5
参数:
storageKey(可选): 存储键名,默认为'storage'options(可选): Cookie 配置选项(参见 高级配置)isEmbedded(可选): 是否为内嵌模式,默认为false。设为true时使用localStorage替代 Cookie
cookieStore.set(key: string, value: any, encrypt?: boolean)
设置数据。
参数:
key: 键名value: 要存储的值(会自动序列化)encrypt(可选): 是否加密存储,默认为 false
cookieStore.get(key: string): any
获取数据,自动反序列化和解密。
cookieStore.remove(key: string)
移除指定的数据。
cookieStore.clear()
清除所有数据。内嵌模式下清除 localStorage 中对应的项,普通模式下移除 Cookie。
高级配置
环境变量配置
可通过环境变量 VITE_JS_COOKIE_OPTIONS 配置 Cookie 选项:
// 在 .env 文件中
VITE_JS_COOKIE_OPTIONS={"path":"/","sameSite":"Strict","secure":true}2
可配置选项:
path: Cookie 路径,默认为 "/"domain: Cookie 域名expires: 过期时间(天数)secure: 是否仅通过 HTTPS 传输sameSite: SameSite 策略("Strict" | "Lax" | "None")
动态配置
import { setSetting } from '@pt/utils/modules/storage';
// 动态设置 Cookie 选项
setSetting({
cookieOptions: {
path: '/',
sameSite: 'Strict',
secure: true,
expires: 7 // 7天后过期
}
});2
3
4
5
6
7
8
9
10
11
完整示例
用户认证场景
import { cookieStore } from '@pt/utils/modules/storage';
interface AuthData {
token: string;
refreshToken: string;
expiresAt: number;
}
// 登录成功后存储认证信息(加密)
function saveAuthData(authData: AuthData) {
cookieStore.set('auth', authData, true);
}
// 获取认证信息
function getAuthData(): AuthData | null {
return cookieStore.get('auth');
}
// 检查是否已登录
function isAuthenticated(): boolean {
const auth = getAuthData();
if (!auth) return false;
return Date.now() < auth.expiresAt;
}
// 登出
function logout() {
cookieStore.remove('auth');
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
多语言偏好设置
interface UserPreferences {
language: string;
theme: 'light' | 'dark';
fontSize: number;
}
// 保存用户偏好
function savePreferences(prefs: UserPreferences) {
cookieStore.set('preferences', prefs);
}
// 加载用户偏好
function loadPreferences(): UserPreferences {
return cookieStore.get('preferences') || {
language: 'zh-CN',
theme: 'light',
fontSize: 14
};
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
安全建议
1. 敏感数据加密
始终对敏感数据使用加密存储:
// ✅ 推荐:加密存储
cookieStore.set('authToken', token, true);
cookieStore.set('apiKey', key, true);
// ❌ 不推荐:明文存储敏感数据
cookieStore.set('password', pwd); // 不要这样做!2
3
4
5
6
2. 设置合适的过期时间
import { setSetting } from '@pt/utils/modules/storage';
// 为敏感数据设置较短的过期时间
setSetting({
cookieOptions: {
expires: 1 // 1天后过期
}
});2
3
4
5
6
7
8
3. 使用 HTTPS
在生产环境中始终使用 secure 标志:
setSetting({
cookieOptions: {
secure: true, // 仅通过 HTTPS 传输
sameSite: 'Strict'
}2
3
4
5
完整示例
用户会话管理
import { cookieStore, setSetting } from '@pt/utils/modules/storage';
// 配置 Cookie 选项
setSetting({
cookieOptions: {
path: '/',
secure: true,
sameSite: 'Strict',
expires: 7 // 7天
}
});
interface UserSession {
userId: string;
username: string;
token: string;
refreshToken: string;
expiresAt: number;
}
class SessionManager {
private readonly SESSION_KEY = 'user_session';
// 保存会话
saveSession(session: UserSession) {
cookieStore.set(this.SESSION_KEY, session, true); // 加密存储
}
// 获取会话
getSession(): UserSession | null {
const session = cookieStore.get(this.SESSION_KEY);
if (!session) return null;
// 检查是否过期
if (Date.now() > session.expiresAt) {
this.clearSession();
return null;
}
return session;
}
// 更新令牌
updateToken(token: string, expiresAt: number) {
const session = this.getSession();
if (session) {
session.token = token;
session.expiresAt = expiresAt;
this.saveSession(session);
}
}
// 清除会话
clearSession() {
cookieStore.remove(this.SESSION_KEY);
}
// 检查是否已登录
isAuthenticated(): boolean {
return this.getSession() !== null;
}
}
export const sessionManager = new SessionManager();2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
跨域 Cookie 配置
import { cookieStore, setSetting } from '@pt/utils/modules/storage';
// 配置跨域 Cookie
setSetting({
cookieOptions: {
domain: '.example.com', // 允许子域名访问
path: '/',
secure: true,
sameSite: 'None' // 允许跨站点请求
}
});
// 在主域名设置 Cookie
cookieStore.set('shared_data', { value: 'data' });
// 子域名可以访问
// sub1.example.com 和 sub2.example.com 都能读取2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
记住我功能
import { cookieStore, setSetting } from '@pt/utils/modules/storage';
interface LoginForm {
username: string;
password: string;
rememberMe: boolean;
}
function handleLogin(form: LoginForm) {
if (form.rememberMe) {
// 记住用户名,30天过期
setSetting({
cookieOptions: { expires: 30 }
});
cookieStore.set('remembered_username', form.username);
} else {
// 清除记住的用户名
cookieStore.remove('remembered_username');
}
// 执行登录逻辑...
}
// 页面加载时恢复用户名
function loadRememberedUsername(): string | null {
return cookieStore.get('remembered_username');
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
常见问题
1. Cookie 大小限制
Cookie 单个值不能超过 4KB,存储大量数据时应考虑使用 Local Storage。
// ❌ 不推荐:存储大量数据
cookieStore.set('largeData', hugeObject);
// ✅ 推荐:使用 Local Storage
localStorage.set('largeData', hugeObject);2
3
4
5
2. 跨域问题
Cookie 受同源策略限制,跨域时需要正确配置。
// 配置允许跨域的 Cookie
setSetting({
cookieOptions: {
domain: '.example.com',
sameSite: 'None',
secure: true // sameSite=None 必须配合 secure=true
}
});2
3
4
5
6
7
8
3. HttpOnly 限制
通过 JavaScript 设置的 Cookie 无法设置 HttpOnly 标志,需要服务端设置。
// ⚠️ 注意:无法通过 JavaScript 设置 HttpOnly
// HttpOnly Cookie 只能由服务端设置
// 用于防止 XSS 攻击窃取 Cookie2
3
性能优化
1. 批量操作
// ❌ 不推荐:多次调用
cookieStore.set('key1', value1);
cookieStore.set('key2', value2);
cookieStore.set('key3', value3);
// ✅ 推荐:合并数据
cookieStore.set('batchData', {
key1: value1,
key2: value2,
key3: value3
});2
3
4
5
6
7
8
9
10
11
2. 减少 Cookie 数量
// ❌ 不推荐:每个配置项单独存储
cookieStore.set('theme', 'dark');
cookieStore.set('language', 'zh-CN');
cookieStore.set('fontSize', 14);
// ✅ 推荐:合并为一个配置对象
cookieStore.set('userPreferences', {
theme: 'dark',
language: 'zh-CN',
fontSize: 14
});2
3
4
5
6
7
8
9
10
11
安全最佳实践总结
- 敏感数据加密:始终对敏感信息使用加密存储
- HTTPS only:生产环境启用
secure标志 - SameSite 保护:使用
Strict或Lax防止 CSRF 攻击 - 合理过期时间:根据数据敏感度设置合适的过期时间
- 最小权限原则:只存储必要的信息
- 定期清理:及时清除不再需要的 Cookie
// 安全配置示例
setSetting({
cookieOptions: {
path: '/',
secure: true, // 仅 HTTPS
sameSite: 'Strict', // 严格的同站策略
expires: 1 // 1天过期
}
});
// 加密存储敏感数据
cookieStore.set('authToken', token, true);
cookieStore.set('apiKey', key, true);
});2
3
4
5
6
7
8
9
10
11
12
13
14