什么是 Cookie?
Cookie 就像是网站给你的一张"会员卡"。当你访问一个网站时,【服务器】可以通过 Cookie 记住你是谁、你的偏好设置,或者你是否已经登录。
扩展:一些网站会弹出一个框,问我们是否接受Cookie,接受和拒绝分别会导致什么?
【接受Cookie】可以提升网站体验(如记住登录状态、个性化推荐),但可能泄露隐私(如追踪浏览习惯用于广告);
【拒绝】则保护隐私但可能导致部分功能受限(如每次需重新登录)。根据你对隐私和便利的权衡决定即可。
Cookie 的基本特性
- 小型文本数据:通常不超过 4KB
- 键值对存储:如
username=jiang - 域名绑定:只能被创建它的域名读取
- 有时效性:可以设置过期时间
Cookie 的工作原理
// 服务器设置Cookie(Node.js Express示例)
res.cookie('username', 'jiang', {
maxAge: 24 * 60 * 60 * 1000, // 1天有效期
httpOnly: true, // 只能通过HTTP访问,JavaScript无法读取
secure: true, // 仅通过HTTPS传输
sameSite: 'Lax' // 防止CSRF攻击
});
// 客户端读取Cookie(浏览器JavaScript)
console.log(document.cookie); // 输出所有cookie(只能获取未设置httpOnly: true的cookie)
Cookie 在认证中的作用
1. 基础登录认证
/**
* 用户登录路由处理
* POST /login - 验证用户凭据并设置登录状态cookie
*/
app.post('/login', (req, res) => {
// 从请求体中获取用户名和密码
const { username, password } = req.body;
// 验证用户凭据
if (validateUser(username, password)) {
// 设置两个cookie:
// 1. auth cookie: 用于认证的标志,设置httpOnly防止XSS攻击
// 2. username cookie: 存储用户名,用于前端显示等用途
res.cookie('auth', 'true', { httpOnly: true });
res.cookie('username', username);
// 返回登录成功响应
res.send('登录成功');
} else {
// 验证失败返回401未授权状态码
res.status(401).send('登录失败');
}
});
/**
* 认证检查中间件
* 检查请求是否已通过认证(检查auth cookie)
* 如果已认证则放行,否则重定向到登录页
*/
function checkAuth(req, res, next) {
// 检查auth cookie是否存在且值为'true'
if (req.cookies.auth === 'true') {
next(); // 已认证,继续处理下一个中间件/路由
} else {
// 未认证,重定向到登录页面
res.redirect('/login');
}
}
2. 会话管理
服务器端会话更安全:
/**
* 会话存储对象
* 键: sessionId (由uuid生成)
* 值: { username: string, loggedInAt: number } 用户会话数据
*/
const sessions = {}; // 使用内存存储会话数据,生产环境建议使用数据库存储
/**
* 用户登录接口
* POST /login
* 请求体: { username: string, password: string }
*/
app.post('/login', (req, res) => {
// 从请求体中获取用户名和密码
const { username, password } = req.body;
// 验证用户凭据
if (validateUser(username, password)) {
// 生成唯一会话ID
const sessionId = uuid.v4();
// 存储会话数据
sessions[sessionId] = {
username, // 登录用户名
loggedInAt: Date.now() // 登录时间戳
};
// 设置HTTP-only Cookie,防止XSS攻击获取sessionId
res.cookie('sessionId', sessionId, { httpOnly: true });
// 返回登录成功响应
res.send('登录成功');
} else {
// validateUser返回false时,这里应该处理登录失败逻辑
// 当前代码缺少这部分处理
}
});
/**
* 用户个人资料接口
* GET /profile
* 需要有效的会话Cookie才能访问
*/
app.get('/profile', (req, res) => {
// 从Cookie中获取sessionId并查找对应会话
const session = sessions[req.cookies.sessionId];
if (session) {
// 会话有效,返回个性化欢迎信息
res.send(`欢迎回来,${session.username}`);
} else {
// 会话无效,重定向到登录页面
res.redirect('/login');
}
});
Cookie 与单点登录(SSO)
在SSO系统中,Cookie扮演着关键角色:
1. SSO服务器Cookie(全局会话)
// SSO服务器登录成功后
app.post('/sso-login', (req, res) => {
// ...这里做一些验证...
const ssoToken = generateSSOToken(user);
res.cookie('sso_session', ssoToken, {
domain: '.jjq.com', // 主域名,所有子域名共享
httpOnly: true,
maxAge: 8 * 60 * 60 * 1000 // 8小时
});
// 重定向回原始服务
res.redirect(`${serviceUrl}?ticket=${ticket}`);
});
功能:
- 这是SSO服务器的登录处理
- 用户验证成功后,服务器创建一个名为'sso_session'的Cookie
- 这个Cookie设置了
domain: '.jjq.com',意味着所有jjq.com的子域名都能读取这个Cookie httpOnly: true表示这个Cookie不能通过JavaScript访问,防止XSS攻击- 有效期设为8小时
作用:
- 这个Cookie表示用户已经在SSO服务器完成了全局登录
- 当用户访问其他关联系统时,系统会检查这个Cookie来判断用户是否已登录
2. 应用本地Cookie(本地会话)
// 应用验证SSO ticket后
app.get('/sso-callback', async (req, res) => {
const { ticket } = req.query;
// 向SSO服务器验证ticket
const validation = await validateTicket(ticket);
if (validation.valid) {
// 创建本地会话
const localToken = generateLocalToken();
storeSession(localToken, validation.user);
res.cookie('app_session', localToken, {
httpOnly: true,
maxAge: 2 * 60 * 60 * 1000 // 2小时
});
res.redirect('/dashboard');
}
});
功能:
- 这是应用系统接收SSO认证结果的回调处理
- 应用系统使用ticket向SSO服务器验证
- 验证通过后,应用系统创建自己的本地会话Cookie 'app_session'
- 这个Cookie只对当前应用有效(没有设置domain属性)
- 有效期设为2小时
作用:
- 这个Cookie表示用户在当前应用系统中的登录状态
- 后续请求中,应用系统只需检查这个本地Cookie,不需要每次都去SSO服务器验证
整体流程【必须对此流程烂熟于心,这个流程是理解SSO的重点】
- 用户访问应用A,发现未登录
- 重定向到SSO登录页面
- 用户登录成功,SSO服务器设置全局Cookie
- SSO服务器生成一次性ticket,重定向回应用A
- 应用A用ticket向SSO服务器验证
- 验证通过后,应用A设置自己的本地Cookie
- 用户访问应用B时,由于全局Cookie存在,SSO服务器可以直接认证,不需要再次登录
这种设计既实现了单点登录,又允许各个应用系统管理自己的会话。
🌟Cookie 安全最佳实践推荐!!
-
HttpOnly:防止XSS攻击读取Cookie
res.cookie('secureCookie', 'value', { httpOnly: true }); -
Secure:仅通过HTTPS传输
res.cookie('secureCookie', 'value', { secure: true }); -
SameSite:防止CSRF攻击
res.cookie('secureCookie', 'value', { sameSite: 'Strict' }); -
域和路径限制
res.cookie('domainCookie', 'value', { domain: 'jiang.com', path: '/admin' }); -
定期更换会话ID
function refreshSession(req, res) { const newSessionId = generateNewId(); migrateSessionData(req.cookies.sessionId, newSessionId); res.cookie('sessionId', newSessionId, { httpOnly: true }); }
【再次回顾一下流程】:SSO系统中的Cookie流程
-
首次访问应用A:
- 无本地Cookie → 重定向到SSO
- SSO检查无全局Cookie → 显示登录页
-
成功登录SSO:
sequenceDiagram 用户->>SSO服务器: 提交凭证 SSO服务器->>用户: 设置sso_session Cookie SSO服务器->>用户: 重定向回应用A带ticket 用户->>应用A: 提交ticket 应用A->>SSO服务器: 验证ticket SSO服务器->>应用A: 返回用户信息 应用A->>用户: 设置app_a_session Cookie -
访问应用B:
sequenceDiagram 用户->>应用B: 访问 应用B->>用户: 检查无本地Cookie,重定向SSO 用户->>SSO服务器: 携带sso_session Cookie SSO服务器->>应用B: 用户已认证,返回ticket 应用B->>用户: 设置app_b_session Cookie
总结
Cookie是现代Web认证的基础,特别是在单点登录系统中:
- SSO全局Cookie:相当于"护照",证明你已通过中央认证
- 应用本地Cookie:相当于"签证",每个应用维护自己的会话
- Ticket:一次性通行证,用于应用和SSO服务器之间的安全通信
理解Cookie如何工作,是理解Web认证和SSO系统的关键基础!!
下次再见~