深入理解Cookie:工作原理与使用实践

0 阅读8分钟

前言

本文讲解一下Cookie的一些基础知识,以及简单应用,最后会通过一个简单的登录的例子来讲解,会使用node作为后端,大家可以进行参考!

什么是Cookie?

Cookie是网站在用户浏览器上存储的小型文本数据,由网站在用户访问时创建并发送到浏览器,浏览器会保存这些数据并在后续请求中将其发送回服务器。Cookie的主要目的是实现状态管理,让无状态的HTTP协议能够"记住"用户和会话信息。

image.png


Cookie的工作原理

  1. 服务器创建:当用户首次访问网站时,服务器通过HTTP响应头Set-Cookie发送Cookie到浏览器
  2. 浏览器存储:浏览器接收到Cookie后会将其存储在本地
  3. 自动发送:之后对该网站的所有请求,浏览器都会自动通过HTTP请求头Cookie将存储的Cookie发送回服务器
  4. 服务器读取:服务器接收到请求后,可以读取Cookie中的信息来识别用户或会话

如何使用Cookie

接下来,作者将通过一个用户登录的简单案例,来讲解Cookie的常见用法--作为登录凭证
(先展示代码,下面会详细讲解)

项目结构:

image.png

后端 server.js

我们先用node.js创建创建服务器写一个简单的后端登录程序:
(由于这里没有连接数据库,就不进行登录逻辑的验证了,直接登录成功,返回Cookie)

const http = require('http');
const fs = require('fs'); 
const path = require('path'); 
const server = http.createServer((req, res) => {
  //首页接口
  if (req.method == 'GET' && 
    (req.url == '/' || req.url == '/index.html')) {
    fs.readFile(//传递两个参数 路径,和回调函数
      path.join(__dirname,'public', 'index.html'), 
      (err, content) => {
      if (err) {
        res.writeHead(500); // 5XX 服务器错误
        res.end('Server error');
        return;
      }
      res.writeHead(200, { 'Content-Type': 'text/html' })
      res.end(content);
    })
  }
  if (req.method == 'GET' && req.url == '/style.css') {
    fs.readFile(
      path.join(__dirname, 'public', 'style.css'),
      (err, content) => {
        if (err) {
          res.writeHead(500);
          res.end('Server error');
          return;
        }
        res.writeHead(200, { 'Content-Type': 'text/css' })
        res.end(content);
      }
    );
    return;
  }

  if (req.method == 'GET' && req.url == '/script.js') {
    fs.readFile(
      path.join(__dirname, 'public', 'script.js'),
      (err, content) => {
        if (err) {
          res.writeHead(500);
          res.end('Server error');
          return;
        }
        res.writeHead(200, { 'Content-Type': 'text/javascript' })
        res.end(content);
      }
    );
    return;
  }
  
  //上述都是基本的获取资源的接口,下面才是业务逻辑的接口,请着重看下面的代码
  
  
  
  
  
  //登录接口
  if (req.method == 'POST' && req.url == '/login'){
    //这里按理来讲要对请求中的账号密码进行验证 由于着重讲解Cookie的使用 就不验证了
    res.writeHead(200,{
      'Set-Cookie': "user=admin",
      'Content-Type': 'application/json'
    })
    res.end(
      JSON.stringify({
        success:true,
        msg:'登录成功'
      })
    )
  }
  //验证登录接口
  if (req.method == "GET" && req.url == "/check-login"){
    const cookies = Object.fromEntries(req.headers.cookie?.split(';').map(c => c.trim().split('=')) || []);
    if(cookies.user === 'admin'){
      res.writeHead(200,{
        'Content-Type': 'application/json'
      })
      res.end(JSON.stringify({
        isLogin:'ok'
      }))
    }
    else {
      res.writeHead(200,{
        'Content-Type': 'application/json'
      })
      res.end(JSON.stringify({
        isLogin:'fail'
      }))
    }
  }
})
server.listen(8080);

前端 index.html + style.css + script.js

展示script.js

const loginForm = document.getElementById('loginForm');
loginForm.addEventListener('submit', async (e) => {
  e.preventDefault();
  const username = document.getElementById('username').value.trim();
  const password = document.getElementById('password').value.trim();
  try{
   const response = await fetch('/login',{
    method:'POST',
    headers: {
      'Content-Type':'application/json'
    },
    body: JSON.stringify({
        username,
        password
    })
   })
   const data = await response.json();
  }
  catch{
    console.log('登录出错了')
  }
})

document.addEventListener('DOMContentLoaded',async ()=>{
    try {
      const response = await fetch('/check-login')
      const data =await response.json();
      console.log(data);
      if(data.isLogin == "ok"){
        document.getElementById('loginSection').style.display = 'none';
        document.getElementById('welcomeSection').style.display = 'block';
        document.getElementById('userDisplay').textContent = data.username;
    }
      else if(data.isLogin == "fail"){
        document.getElementById('loginSection').style.display = 'block';
        document.getElementById('welcomeSection').style.display = 'none';
      }
    }catch{
      
    }
})

分析代码流程

让我们来分析代码流程,分析Cookie在这个登录程序中的作用吧

首页展示

首先当我们输入localhost:8080时,会得到以下的页面, image.png

这是我们项目结构中的html和css部分,实际上,还是通过server.js后端服务拿到页面资源,它识别到http://localhost:8080/返回首页资源;它识别到http://localhost:8080/style.css返回css资源;它识别到http://localhost:8080/script.js,返回js资源,然后浏览器就可以拿到这些,通过渲染构建出我们想看的画面,html和css部分加上script.js我们就称为前端代码了,用于渲染页面(html+css)以及给后端发送请求(script.js)。

image.png

相关代码

  //首页接口
  if (req.method == 'GET' && 
    (req.url == '/' || req.url == '/index.html')) {
    fs.readFile(//传递两个参数 路径,和回调函数
      path.join(__dirname,'public', 'index.html'), 
      (err, content) => {
      if (err) {
        res.writeHead(500); // 5XX 服务器错误
        res.end('Server error');
        return;
      }
      res.writeHead(200, { 'Content-Type': 'text/html' })
      res.end(content);
    })
  }
  if (req.method == 'GET' && req.url == '/style.css') {
    fs.readFile(
      path.join(__dirname, 'public', 'style.css'),
      (err, content) => {
        if (err) {
          res.writeHead(500);
          res.end('Server error');
          return;
        }
        res.writeHead(200, { 'Content-Type': 'text/css' })
        res.end(content);
      }
    );
    return;
  }

  if (req.method == 'GET' && req.url == '/script.js') {
    fs.readFile(
      path.join(__dirname, 'public', 'script.js'),
      (err, content) => {
        if (err) {
          res.writeHead(500);
          res.end('Server error');
          return;
        }
        res.writeHead(200, { 'Content-Type': 'text/javascript' })
        res.end(content);
      }
    );
    return;
  }

登录请求

当我们在表单中输入账号密码,点击Login时

image.png

此刻会发送POST请求login给后端服务器server.js

image.png

服务器会返回请求体为JSON的response给前端

image.png

并且注意哦,这个时候服务器返回的response也会携带Cookie哦!我们现在可以在浏览器中看到服务器返回的Cookie呢

image.png

相关代码:

  if (req.method == 'POST' && req.url == '/login'){
    //这里按理来讲要对请求中的账号密码进行验证 由于着重讲解Cookie的使用 就不验证了
    res.writeHead(200,{
      'Set-Cookie': "user=admin",  //response中携带Cookie
      'Content-Type': 'application/json'
    })
    res.end(
      JSON.stringify({
        success:true,
        msg:'登录成功'
      })
    )
  }

登录验证

此时我们再刷新页面,可以看到下面的效果:

image.png

这是因为刷新页面时,给后端服务发送了check-login的请求

image.png

并且能够看到,此时前端发送的请求中携带了Cookie,后端的check-login的接口验证发现有这个Cookie,于是就返回一个成功的response,于是客户端就可以更改页面结构了! image.png

相关代码:

  if (req.method == "GET" && req.url == "/check-login"){
    const cookies = Object.fromEntries(req.headers.cookie?.split(';').map(c => c.trim().split('=')) || []);
    if(cookies.user === 'admin'){
      res.writeHead(200,{
        'Content-Type': 'application/json'
      })
      res.end(JSON.stringify({
        isLogin:'ok'
      }))
    }
    else {
      res.writeHead(200,{
        'Content-Type': 'application/json'
      })
      res.end(JSON.stringify({
        isLogin:'fail'
      }))
    }
  }

这里的代码略微有点复杂,我们需要分析一下: 首先识别到前端发送了check-login请求,于是进入请求处理:

将前端发送的Cookie分析一下:

Pycharm-843fbaf3=1bf2ebc3-3548-4f69-9583-e1cdac448210; Webstorm-e54c2519=9edffdde-495d-4c06-8b16-9f2d01a20e72; user=admin
  1. 按 ; 分割:将整个 Cookie 字符串拆分成多个键值对
  2. 去除空格并拆分键值:对每个键值对进行 trim() 和 split('=')
  3. 转换为对象:使用 Object.fromEntries() 将数组转为对象

输出结果:

{
  "Pycharm-843fbaf3": "1bf2ebc3-3548-4f69-9583-e1cdac448210",
  "Webstorm-e54c2519": "9edffdde-495d-4c06-8b16-9f2d01a20e72",
  "user": "admin"
}

通过解析Cookie并验证,发现前端存在刚刚后端服务给出的Cookie,所以返回isLogin:'ok'
前端接收到后端返回的isLogin:'ok'之后,进行页面的处理:

if(data.isLogin == "ok"){
        document.getElementById('loginSection').style.display = 'none';
        document.getElementById('welcomeSection').style.display = 'block';
        document.getElementById('userDisplay').textContent = data.username;
    }
      else if(data.isLogin == "fail"){
        document.getElementById('loginSection').style.display = 'block';
        document.getElementById('welcomeSection').style.display = 'none';
      }

所以就达到了我们想要的效果!


总结这个案例中Cookie的作用

在这个登录功能的案例中,Cookie 扮演了以下几个关键角色:

1. 身份认证的凭证

  • 当用户首次登录成功时,服务器通过Set-Cookie响应头返回一个Cookie(user=admin
  • 这个Cookie就相当于服务器颁发的"身份证明",后续请求中浏览器会自动携带这个Cookie

2. 会话状态的维持

  • 浏览器会存储这个Cookie(默认在关闭浏览器前都有效)
  • 每次刷新页面时,浏览器会自动在请求头中带上这个Cookie(Cookie: user=admin
  • 这样服务器就能识别出这是已经登录过的用户

3. 权限验证的依据

  • 在访问/check-login接口时:

    • 服务器解析请求中的Cookie
    • 验证是否存在user=admin这个键值对
    • 根据验证结果返回不同的登录状态

4. 实现"免登录"效果

  • 因为Cookie会被浏览器持久化存储:

    • 即使用户关闭页面再打开,只要Cookie未过期
    • 系统仍然能识别出已登录状态
    • 不需要用户重复输入账号密码

关键流程总结

  1. 首次登录 → 服务器下发Cookie
  2. 后续请求 → 浏览器自动携带Cookie
  3. 状态检查 → 服务器验证Cookie有效性
  4. 界面展示 → 根据Cookie验证结果动态显示内容

Cookie的替代方案

虽然Cookie非常有用,但在某些场景下可能需要考虑替代方案:

  1. Web Storage API(localStorage/sessionStorage):

    • 更大的存储空间(通常5MB)
    • 数据不会自动发送到服务器
    • 更适合纯客户端存储
  2. IndexedDB

    • 客户端数据库,适合存储大量结构化数据
  3. JWT(JSON Web Token)

    • 无状态认证方案
    • 可以存储在Cookie、localStorage或内存中

实际应用场景

  1. 用户认证:存储Session ID或Token
  2. 个性化设置:存储用户的语言、主题偏好等
  3. 购物车功能:临时存储用户选择的商品
  4. 跟踪和分析:用户行为分析(需注意隐私合规)
  5. 跨页面状态保持:在多页面应用中保持状态

总结

Cookie是Web开发中不可或缺的技术,它解决了HTTP无状态的问题,使得网站能够"记住"用户和会话信息。正确使用Cookie需要理解其工作原理、安全属性和最佳实践。随着Web技术的发展,虽然出现了Web Storage等替代方案,但Cookie在特定场景下仍然是不可替代的解决方案。

在实际开发中,应根据具体需求合理选择存储方案,并始终将安全性放在首位,遵循最小权限原则,保护用户数据和隐私。