Cookie/Session 与 Remix 中处理 Cookie/Session

什么是 cookie ?

模块 界说
了解 Cookie 是一小块 数据(服务端发送给阅读器)。阅读器存储 Cookie 用于与服务端之间传输数据。
效果 坚持会话(登录状况)-个性化设置(主题)-阅读器行为追寻(分析用户行为)
品种 会话(阅读器封闭就删去)、持久(登录状况等)、第一方(本网站)/第三方(其他域名)、安全(HTTPS、httpOnly、SmaeSite Cookie)

如何运用 Cookie

留意: Cookie 运用要安全,通常需求 加密

cookie 设置特点

称号 类型 描绘
Name String Cookie 的称号,用于标识 Cookie。
Value String Cookie 的值,包括该 Cookie 的详细信息。
Expires/Max-Age Date/Number Cookie 的过期时刻,能够经过设置 Expires 或者 Max-Age 特点来指定。假如不设置,那么该 Cookie 将在阅读器封闭时主动删去。
Domain String Cookie 所属的域名。假如不指定,那么该 Cookie 将仅适用于设置它的域名。
Path String Cookie 所在的途径。假如不指定,那么该 Cookie 将仅适用于设置它的途径。
Secure Boolean 一个布尔值,表示是否仅在运用 HTTPS 协议时发送 Cookie。假如设置为 true,则该 Cookie 仅适用于 HTTPS 协议。
HttpOnly Boolean 一个布尔值,表示是否允许客户端经过 JavaScript 拜访该 Cookie。假如设置为 true,则该 Cookie 仅适用于 HTTP 协议,并且无法经过 JavaScript 拜访。

阅读器读取 Cookie 和解析 Cookie

览器供给了一个名为 document.cookie 的 API,能够用于读取和设置 Cookie。

const cookies = document.cookie.split("; ");
cookies.forEach((cookie) => {
  const [name, value] = cookie.split("=");
  console.log(`${name}=${value}`);
});

以 Node.js 为比如运用 Cookie

import http from 'node:http'
const server = http.createServer((req, res) => { {
    // 在呼应头中设置 Cookie
    const server = http.createServer((req, res) => { // 服务器呼应恳求的逻辑代码 });
    // 从恳求头中读取 Cookie:
    const cookie = req.headers.cookie;
    // ...
});
server.listen(3000, () => { console.log('Server is running on port 3000'); });

当然假如你运用 express 等结构会供给愈加简单的操作 cookie 办法:cookie-parser

cookie 的约束

约束/特点 描绘 格式/可选值
巨细约束 每个 Cookie 的巨细 4KB 以内
数量约束 每个域名下 Cookie 的数量 50 个以内
安全约束 Secure true/false
安全约束 HttpOnly true/false
有用期约束 Expires 格式:Wdy, DD-Mon-YYYY HH:MM:SS GMT
有用期约束 Max-Age 单位:秒
途径约束 Path Cookie 的途径

cookie 运用场景

运用场景 描绘
身份验证 经过在用户登录时创立 Cookie 来标识用户身份,运用户能够在会话期间坚持登录状况。
记住暗码 创立一个包括用户名和暗码的 Cookie,使得用户能够在下一次登录时免输入账号暗码。
记载用户偏好 在 Cookie 中存储用户的偏好设置,例如语言、主题、字体巨细等,以便用户下次拜访时康复相应的设置。
购物车 在 Cookie 中存储用户加入购物车的商品信息,以便用户能够在下次拜访时康复购物车状况。
个性化推荐 依据用户的前史阅读记载和偏好设置,在 Cookie 中存储相关数据,以便进行个性化推荐。
统计分析 在 Cookie 中存储用户行为数据,例如页面拜访次数、停留时刻等,以便进行统计分析和用户行为分析。

依据 JS 封装 Cookie 类

class MyCookie {
  static set(name, value, options = {}) {
    const { expires, path, domain, secure } = options;
    document.cookie =
      `${name}=${encodeURIComponent(value)}` +
      (expires ? `; expires=${expires.toUTCString()}` : "") +
      (path ? `; path=${path}` : "") +
      (domain ? `; domain=${domain}` : "") +
      (secure ? "; secure" : "");
  }
  static get(name) {
    const cookieArr = document.cookie.split("; ");
    for (let i = 0; i < cookieArr.length; i++) {
      const [cookieName, cookieValue] = cookieArr[i].split("=");
      if (cookieName === name) {
        return decodeURIComponent(cookieValue);
      }
    }
    return null;
  }
  static delete(name, options = {}) {
    const { path, domain } = options;
    document.cookie =
      `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC` +
      (path ? `; path=${path}` : "") +
      (domain ? `; domain=${domain}` : "");
  }
}
// 测验用例示例
describe("Cookie", () => {
  // 测验设置 Cookie
  it("应该运用给定的称号、值和选项设置一个 Cookie", () => {
    Cookie.set("test", "123", {
      expires: new Date(Date.now() + 86400000),
      path: "/",
      domain: "example.com",
      secure: true,
    });
    const cookie = document.cookie;
    expect(cookie).toMatch("test=123");
    expect(cookie).toMatch("expires");
    expect(cookie).toMatch("path=/");
    expect(cookie).toMatch("domain=example.com");
    expect(cookie).toMatch("secure");
  });
  // 测验获取 Cookie
  it("应该返回给定称号的 Cookie 的值", () => {
    document.cookie = "test=123";
    const value = Cookie.get("test");
    expect(value).toBe("123");
  });
  // 测验删去 Cookie
  it("应该删去具有给定称号和选项的 Cookie", () => {
    Cookie.delete("test", {
      path: "/",
      domain: "example.com",
    });
    const cookie = document.cookie;
    expect(cookie).not.toMatch("test=123");
  });
});

Remix 中如何处理 Cookie

Remix 中对 Cookie 进行了封装, Remix 供给了 Cookie API

api 和特点

api 导入

import { isCookie, createCookie } from "@remix-run/node";

特点

const cookie = createCookie();
cookie.name; // 设置 Cookie 的名字
cookie.isSinged; // 是否运用了 secrets
cookie.expires; // 过期时刻

创立 createCookie

import { createCookie } from "@remix-run/node"; // or cloudflare/deno
export const userPrefs = createCookie("user-prefs", {
  maxAge: 604_800, // one week
});
// 加密
const cookie = createCookie("user-prefs", {
  secrets: ["s3cret1"],
});

userPrefs 具有: parse 解析/serialize 序列化, 配合 Remix loader/action 来运用

import { userPrefs } from "~/cookies";
// loader/action
const cookieHeader = request.headers.get("Cookie");
const cookie = (await userPrefs.parse(cookieHeader)) || {};
// 序列化
redirect("/", {
    headers: {
      "Set-Cookie": await userPrefs.serialize(cookie),
    })

判别是否 isCookie

import { isCookie } from "@remix-run/node";
const cookie = createCookie("user-prefs");
console.log(isCookie(cookie)); // true

Remix 官方运用示例

示例:gdpr-cookie-consent

  • 设置 cookie
import { createCookie } from "@remix-run/node";
export const gdprConsent = createCookie("gdpr-consent", {
  maxAge: 31536000, // One Year
});
  • 处理 cookie
import type { ActionArgs } from "@remix-run/node";
import { json } from "@remix-run/node";
import { gdprConsent } from "~/cookies";
export const action = async ({ request }: ActionArgs) => {
  const formData = await request.formData();
  const cookieHeader = request.headers.get("Cookie");
  const cookie = (await gdprConsent.parse(cookieHeader)) || {};
  if (formData.get("accept-gdpr") === "true") {
    cookie.gdprConsent = true;
  }
  return json(
    { success: true },
    {
      headers: {
        "Set-Cookie": await gdprConsent.serialize(cookie),
      },
    }
  );
};

假如只是运用 cookie 完成登录

长处:

  • 不需求频频输入账号暗码,直接经过 cookie 传输

缺陷:

  • 不安全,简单被篡改
  • 假如多页面,需求频频读取 cookie 形成各种风险

依据 Cookie 的缺陷,这儿我们就需求引出 Session 绘画来弥补 Cookie 在登录等事务上的缺陷。

Session

Cookie/Session 与 Remix 中处理 Cookie/Session

Session 是一个在 Web 运用中跟踪用户会话状况的机制。当用户第一次拜访 Web 运用时,服务器会创立一个唯一的 Session ID,并将其存储在 Cookie 中返回给客户端。

Cookie-Session 作为一种 最简单 的鉴权办法, 广泛被运用。

Session 的主要效果

在服务器端存储用户的状况信息,这些信息能够是任何类型的数据,例如登录信息、购物车内容、用户配置等。

Session 的生命周期

阶段 描绘
创立阶段 当用户第一次拜访网站时,服务器会创立一个唯一的 Session ID,并将该 ID 存储在 Cookie 中返回给客户端。
活动阶段 在用户会话期间,客户端的每个恳求都会带上 Cookie 中存储的 Session ID。服务器运用 Session ID 来获取对应的 Session 数据,以此来坚持用户状况和跟踪用户活动。
过期阶段 一般情况下,Session 有一个过期时刻,假如用户在一段时刻内没有活动,则服务器会删去该 Session 数据,以释放资源。

Session 的存储办法

Session 的存储办法 说明
Cookie-based 将 Session ID 存储在客户端阅读器的 Cookie 中,每次恳求时带着 Cookie 进行验证
Server-side 将 Session ID 及其对应的数据存储在服务器端,客户端恳求时带着 Session ID 进行验证

Session 缺陷

  • 有被绑架 Session ID 的可能,对 Session ID 的强壮性加强,也能够经过定期更换和有用的加密办法的等办法处理
  • Session 与设备之间存在一对多,和多对一的联系,所以同享和同步的问题需求正确的处理。

Session 运用场景

Session 在 Web 运用中的运用 描绘
用户认证和授权 Session 能够存储用户登录状况和权限信息,完成用户认证和授权
购物车和在线付出 Session 能够存储用户的购物车信息和付出状况,完成购物车和在线付出等功用
分布式运用的集群部署 Session 需求完成同享和同步,以确保多个运用实例之间的数据一致性
Session 绑架和防范 Session 可能被黑客进犯和绑架,需求采纳相应的安全措施
Session 同享和同步 分布式运用部署时,需求完成 Session 同享和同步,以确保多个运用实例之间的数据一致性
Session 数据加密和保护 为了提高数据的安全性,需求对 Session 数据进行加密和保护

Session 优化

Session 的优化和功用 描绘
存储和读取优化 运用内存数据库如 Redis、Memcached 存储 Session 数据,以削减数据库的读写次数,并对 Session 数据进行序列化和反序列化
过期时刻和整理战略 依据事务需求和体系功用进行调整,例如依据用户活跃度、Session 数据量和服务器负载等进行设置
缓存和 Session 结合运用 运用分布式缓存如 Redis、Memcached 存储 Session 数据和缓存页面数据,以提高体系的呼应速度和并发拜访量

Remix 中 session

功用 办法名 描绘
创立 session 目标 createSession 创立一个 session 目标
创立 session 存储库 createSessionStorage 能够方便地将 session 数据存储到外部数据库,而不是本机内存中
创立内存 session 存储库 createMemorySessionStorage 将所有的 session 数据保存在服务器内存中,速度较快但不行安全
创立依据文件的 session 存储库 createFileSessionStorage 能够将 session 数据保存到文件中,相对于内存存储愈加稳定和安全,但速度较慢

目前 Remix 也支撑了 Cloudflare Workder 和 Amazon Session 存储。

Remix 中 Session 规划与 Cookie 类似,运用办法也类似:

import { isSession, createSession } from "@remix-run/node";
// 界说 session 数据,创立 session 目标
const sessionData = { foo: "bar" };
const session = createSession(sessionData, "remix-session");
console.log(isSession(session));
session.has;
session.set;
session.flash; // `session.flash` 是一种在 Web 运用中完成音讯提示的机制。
session.get;
session.unset;

在 Loader 和 Action 中运用 Session

import { commitSession, getSession } from "../sessions";
export async function action({ params, request }: ActionArgs) {
  // 从 getSession 和 Cookie 中获取 session
  const session = await getSession(request.headers.get("Cookie"));
  const deletedProject = await archiveProject(params.projectId);
  // 设置首次读取时将取消设置的会话值
  session.flash(
    "globalMessage",
    `Project ${deletedProject.name} successfully archived`
  );
  return redirect("/dashboard", {
    headers: {
      "Set-Cookie": await commitSession(session), // 提交 session
    },
  });
}

session 示例

  • session-flash
  • redis-upstash-session

小结

本文主要回忆了 cookie/session 的基础,然后运用了 Remix 中 cookie/session 的用法优缺陷,是运用 createCookie 函数创立 cookie, 以及特点和办法, 运用各种不的 session 创立办法并创立不同的 session 和其优缺陷以及 Remix 官方创立的示例。

微信查找并关注公踪号进二开物,更多技能 JS/TS/CSS/Rust 文章…