Skip to content

Cookie JS 实操 ​

浏览器 JS ​

写入、读取、更新与删除 ​

document.cookie 只返回当前页面可访问的键值对。一次赋值只设置一个 Cookie。以下示例使用当前主机的根路径保存界面偏好,并约定用 URI 编码保存值。

js
/** 读取示例使用 URI 编码保存的偏好值 */
function readTheme() {
  const entry = document.cookie
    .split(';')
    .map((part) => part.trim())
    .find((part) => part.startsWith('theme='))
  return entry === undefined ? undefined : decodeURIComponent(entry.slice('theme='.length))
}

document.cookie = 'theme=' + encodeURIComponent('浅色') + '; Path=/; Max-Age=86400; SameSite=Lax'
console.log(readTheme())

document.cookie = 'theme=' + encodeURIComponent('深色') + '; Path=/; Max-Age=86400; SameSite=Lax'
document.cookie = 'theme=; Path=/; Max-Age=0; SameSite=Lax'

更新和删除要复用原来的名称、域和路径。示例省略 Domain,使用 host-only 作用域。HttpOnly Cookie 由服务端设置和清除。

跨源请求 ​

本例的页面源是 http://localhost:5173,API 是 http://localhost:3000。二者跨源但同站,适合观察凭证模式和 CORS。请求中的自定义头会触发预检。

js
const api = 'http://localhost:3000'

await fetch(api + '/cookies', {
  method: 'POST',
  credentials: 'include',
  headers: { 'X-Demo': '1' },
})

const response = await fetch(api + '/cookies', { credentials: 'include' })
console.log(await response.json())

const xhr = new XMLHttpRequest()
xhr.open('GET', api + '/cookies')
xhr.withCredentials = true
xhr.send()

浏览器按策略填充 Cookie 请求头,脚本不能手动设置它,也无法通过 response.headers 或 XHR 读取 Set-Cookie。用浏览器开发者工具的 Network 和 Cookie 存储面板分别检查响应头、存储结果和下一次请求。

Node.js 服务端 ​

读取、设置、删除与 CORS ​

将以下代码保存为 server.mjs,通过 node server.mjs 运行。服务只监听本机,写入的是演示 Cookie。响应中的数组让 Node.js 发送独立的 Set-Cookie 字段。DELETE /cookies 只清除 theme,保留 density。

js
import { createServer } from 'node:http'

const allowedOrigin = 'http://localhost:5173'
const cookieScope = 'Path=/; SameSite=Lax'

/** 返回同名 Cookie 的全部原始值 */
function readCookieValues(header, name) {
  const prefix = name + '='
  return (header ?? '')
    .split(';')
    .map((part) => part.trim())
    .filter((part) => part.startsWith(prefix))
    .map((part) => part.slice(prefix.length))
}

/** 处理演示 Cookie 和该接口的 CORS 请求 */
function handleCookies(req, res) {
  const { method, url, headers } = req
  res.setHeader('Vary', 'Origin')
  res.setHeader('Cache-Control', 'no-store')

  if (url !== '/cookies') {
    return res.writeHead(404).end()
  }
  if (headers.origin && headers.origin !== allowedOrigin) {
    return res.writeHead(403).end()
  }
  if (headers.origin === allowedOrigin) {
    res.setHeader('Access-Control-Allow-Origin', allowedOrigin)
    res.setHeader('Access-Control-Allow-Credentials', 'true')
  }

  if (method === 'OPTIONS') {
    res.setHeader('Vary', 'Origin, Access-Control-Request-Method, Access-Control-Request-Headers')
    const requestedMethod = headers['access-control-request-method']
    const requestedHeaders = (headers['access-control-request-headers'] ?? '')
      .toLowerCase()
      .split(',')
      .map((name) => name.trim())
      .filter(Boolean)

    if (
      headers.origin !== allowedOrigin ||
      !['GET', 'POST', 'DELETE'].includes(requestedMethod) ||
      requestedHeaders.some((name) => name !== 'x-demo')
    ) {
      return res.writeHead(403).end()
    }

    res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE')
    res.setHeader('Access-Control-Allow-Headers', 'X-Demo')
    return res.writeHead(204).end()
  }

  if (method === 'GET') {
    res.setHeader('Content-Type', 'application/json')
    return res.end(JSON.stringify({ themes: readCookieValues(headers.cookie, 'theme') }))
  }

  if (method === 'POST' || method === 'DELETE') {
    if (headers.origin !== allowedOrigin || headers['x-demo'] !== '1') {
      return res.writeHead(403).end()
    }

    const cookies =
      method === 'POST'
        ? ['theme=dark; ' + cookieScope + '; Max-Age=86400', 'density=compact; ' + cookieScope + '; Max-Age=86400']
        : ['theme=; ' + cookieScope + '; Max-Age=0']

    res.setHeader('Set-Cookie', cookies)
    return res.writeHead(204).end()
  }

  return res.writeHead(405, { Allow: 'GET, POST, DELETE, OPTIONS' }).end()
}

createServer(handleCookies).listen(3000, 'localhost')

跨源删除请求使用与设置请求相同的 credentials: 'include' 和 X-Demo。生产环境的认证 Cookie 应经 HTTPS 发送,使用 Secure; HttpOnly,并根据应用的部署关系设置 SameSite。真正跨站的凭证请求仍受浏览器的第三方 Cookie 策略约束。

参考 WHATWG Fetch 标准 和 Node.js HTTP 文档。

基于 MIT 许可发布