📨Cookie 完全指南:从基础到单点登录应用

479 阅读5分钟

什么是 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的重点】

  1. 用户访问应用A,发现未登录
  2. 重定向到SSO登录页面
  3. 用户登录成功,SSO服务器设置全局Cookie
  4. SSO服务器生成一次性ticket,重定向回应用A
  5. 应用A用ticket向SSO服务器验证
  6. 验证通过后,应用A设置自己的本地Cookie
  7. 用户访问应用B时,由于全局Cookie存在,SSO服务器可以直接认证,不需要再次登录

这种设计既实现了单点登录,又允许各个应用系统管理自己的会话。

🌟Cookie 安全最佳实践推荐!!

  1. HttpOnly:防止XSS攻击读取Cookie

    res.cookie('secureCookie', 'value', { httpOnly: true });
    
  2. Secure:仅通过HTTPS传输

    res.cookie('secureCookie', 'value', { secure: true });
    
  3. SameSite:防止CSRF攻击

    res.cookie('secureCookie', 'value', { sameSite: 'Strict' });
    
  4. 域和路径限制

    res.cookie('domainCookie', 'value', { 
      domain: 'jiang.com',
      path: '/admin' 
    });
    
  5. 定期更换会话ID

    function refreshSession(req, res) {
      const newSessionId = generateNewId();
      migrateSessionData(req.cookies.sessionId, newSessionId);
      res.cookie('sessionId', newSessionId, { httpOnly: true });
    }
    

【再次回顾一下流程】:SSO系统中的Cookie流程

  1. 首次访问应用A

    • 无本地Cookie → 重定向到SSO
    • SSO检查无全局Cookie → 显示登录页
  2. 成功登录SSO

    sequenceDiagram
      用户->>SSO服务器: 提交凭证
      SSO服务器->>用户: 设置sso_session Cookie
      SSO服务器->>用户: 重定向回应用A带ticket
      用户->>应用A: 提交ticket
      应用A->>SSO服务器: 验证ticket
      SSO服务器->>应用A: 返回用户信息
      应用A->>用户: 设置app_a_session Cookie
    
  3. 访问应用B

    sequenceDiagram
      用户->>应用B: 访问
      应用B->>用户: 检查无本地Cookie,重定向SSO
      用户->>SSO服务器: 携带sso_session Cookie
      SSO服务器->>应用B: 用户已认证,返回ticket
      应用B->>用户: 设置app_b_session Cookie
    

总结

Cookie是现代Web认证的基础,特别是在单点登录系统中:

  1. SSO全局Cookie:相当于"护照",证明你已通过中央认证
  2. 应用本地Cookie:相当于"签证",每个应用维护自己的会话
  3. Ticket:一次性通行证,用于应用和SSO服务器之间的安全通信

理解Cookie如何工作,是理解Web认证和SSO系统的关键基础!!

下次再见~