Cookie 操作与安全设置
Cookie 的基本概念
Cookie 是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。Cookie 主要用于会话状态管理、个性化设置以及行为跟踪等场景。在 Koa2 中,Cookie 的操作通常通过 ctx.cookies
接口实现。
// 设置一个简单的 Cookie
ctx.cookies.set('username', 'JohnDoe', {
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000 // 1天
});
Koa2 中的 Cookie 操作
Koa2 提供了 ctx.cookies
对象来方便地操作 Cookie。这个对象提供了 get
和 set
方法,分别用于读取和设置 Cookie。
设置 Cookie
设置 Cookie 时可以指定多个选项:
ctx.cookies.set('token', 'abc123', {
domain: '.example.com', // 可访问的域名
path: '/admin', // 可访问的路径
maxAge: 86400000, // 有效期(毫秒)
expires: new Date('2023-12-31'), // 过期时间
httpOnly: true, // 仅HTTP访问
secure: true, // 仅HTTPS传输
sameSite: 'strict' // 同站策略
});
读取 Cookie
读取 Cookie 非常简单:
const username = ctx.cookies.get('username');
console.log(username); // 输出: JohnDoe
删除 Cookie
删除 Cookie 实际上是通过设置一个过期时间为过去的时间来实现的:
ctx.cookies.set('username', null, {
maxAge: -1
});
Cookie 的安全设置
Cookie 的安全设置对于防止跨站脚本攻击(XSS)和跨站请求伪造(CSRF)至关重要。
HttpOnly 标志
ctx.cookies.set('sessionId', 's3cr3t', {
httpOnly: true // 防止JavaScript访问
});
Secure 标志
ctx.cookies.set('auth', 'token123', {
secure: true // 仅在HTTPS连接中传输
});
SameSite 属性
SameSite 属性可以防止CSRF攻击:
ctx.cookies.set('csrftoken', 'randomvalue', {
sameSite: 'strict' // 或 'lax'
});
Cookie 签名
Koa2 支持对 Cookie 进行签名,防止客户端篡改:
// 在应用初始化时设置签名密钥
app.keys = ['my secret key'];
// 设置签名Cookie
ctx.cookies.set('cart', JSON.stringify(items), {
signed: true
});
// 读取签名Cookie
const cart = ctx.cookies.get('cart', { signed: true });
Cookie 与 Session
在 Koa2 中,Cookie 常用来实现 Session:
const sessionId = ctx.cookies.get('sessionId', { signed: true });
if (!sessionId) {
const newSessionId = generateSessionId();
ctx.cookies.set('sessionId', newSessionId, {
signed: true,
httpOnly: true
});
// 初始化session数据
}
常见问题与解决方案
Cookie 大小限制
单个 Cookie 通常不应超过 4KB。对于大量数据,可以考虑:
// 将数据存储在服务端,仅通过Cookie传递标识符
ctx.cookies.set('dataRef', 'unique-id', {
httpOnly: true
});
// 在服务端存储实际数据
dataStore.set('unique-id', largeData);
多域名共享 Cookie
ctx.cookies.set('shared', 'value', {
domain: '.example.com' // 所有子域名共享
});
浏览器兼容性
对于不支持 SameSite 的老旧浏览器,需要额外措施:
// 设置双重Cookie
ctx.cookies.set('legacy_session', 'value', {
secure: true,
httpOnly: true
});
ctx.cookies.set('__Secure-session', 'value', {
secure: true,
httpOnly: true,
sameSite: 'none'
});
性能优化
减少 Cookie 数量
合并多个小Cookie:
// 不推荐
ctx.cookies.set('pref1', 'value1');
ctx.cookies.set('pref2', 'value2');
// 推荐
const preferences = {
pref1: 'value1',
pref2: 'value2'
};
ctx.cookies.set('preferences', JSON.stringify(preferences));
静态资源域名分离
为静态资源使用无Cookie域名:
<!-- 主站 -->
<script src="https://www.example.com/app.js"></script>
<!-- 静态资源使用独立域名 -->
<img src="https://static.example.com/image.jpg">
实际应用示例
购物车实现
// 添加商品到购物车
router.post('/cart/add', async (ctx) => {
const { productId, quantity } = ctx.request.body;
let cart = JSON.parse(ctx.cookies.get('cart') || {};
cart[productId] = (cart[productId] || 0) + quantity;
ctx.cookies.set('cart', JSON.stringify(cart), {
maxAge: 30 * 24 * 60 * 60 * 1000, // 30天
httpOnly: true,
signed: true
});
ctx.body = { success: true };
});
用户偏好设置
// 保存用户主题偏好
router.post('/preferences/theme', async (ctx) => {
const { theme } = ctx.request.body;
ctx.cookies.set('userPrefs', JSON.stringify({
...JSON.parse(ctx.cookies.get('userPrefs') || '{}'),
theme
}), {
maxAge: 365 * 24 * 60 * 60 * 1000, // 1年
httpOnly: true
});
ctx.body = { success: true };
});
安全最佳实践
- 始终为敏感Cookie设置HttpOnly和Secure标志
- 对重要数据使用签名Cookie
- 合理设置SameSite属性
- 避免在Cookie中存储敏感信息
- 定期轮换签名密钥
- 设置合理的过期时间
// 安全Cookie示例
ctx.cookies.set('authToken', generateToken(), {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 3600000, // 1小时
signed: true
});
调试与测试
查看Cookie
在Chrome开发者工具的Application面板中可以查看和修改Cookie。
单元测试示例
const request = require('supertest');
const app = require('../app');
describe('Cookie测试', () => {
it('应该设置Cookie', async () => {
const res = await request(app)
.get('/set-cookie')
.expect(200);
expect(res.headers['set-cookie']).toBeDefined();
});
});
与其他技术的结合
与JWT结合
// 设置JWT作为HttpOnly Cookie
ctx.cookies.set('jwt', generateJWT(user), {
httpOnly: true,
secure: true,
maxAge: 3600000
});
// 在中间件中验证
const jwtMiddleware = async (ctx, next) => {
const token = ctx.cookies.get('jwt');
if (!token) {
ctx.throw(401, '未授权');
}
try {
ctx.state.user = verifyJWT(token);
await next();
} catch (err) {
ctx.throw(401, '无效令牌');
}
};
与CSRF防护结合
// 生成CSRF令牌
const csrf = require('koa-csrf');
app.use(csrf());
// 在表单中包含CSRF令牌
router.get('/form', async (ctx) => {
ctx.body = `
<form action="/submit" method="POST">
<input type="hidden" name="_csrf" value="${ctx.csrf}">
<!-- 其他表单字段 -->
</form>
`;
});
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:多种内容类型的响应处理
下一篇:Session 管理的实现方案