[译] 在 React 中了解 Cookie 和 Session

在本文中,我们将探讨 React 中 cookie 和 session 的实现和最佳实践。

Cookie 和 session 是 web 开发中不可或缺的部分,它们是管理用户数据、身份认证和状态的媒介。

Cookie 是存储在用户 web 浏览器上的小块数据(最多 4KB)。Cookie 的典型示例如下(这是 Google Analytics - _ga - Cookie):

js 复制代码
Name: _ga
Value: GA1.3.210706468.1583989741
Domain: .example.com
Path: /
Expires / Max-Age: 2022-03-12T05:12:53.000Z

Cookie 只是包含键值对的字符串。

"Session" 是指用户浏览网站的会话,它代表用户在这一段时间内的连续活动。

在 React 中,cookie 和 session 可以帮助我们创建强大而安全的应用。

了解 cookie 和 session 的基础知识是开发用户交互友好的 web 应用的基础。

本节将深入探讨 cookie 和 session 的概念,探索它们的类型、生命周期和典型使用场景。

Cookie 主要是维护客户端和服务器之间状态数据,通过请求来维护。通过 cookie,可以在用户设备上存储和检索数据,从而提供更加个性化/无缝的浏览体验。

Cookie 有多种类型,每种类型都能很好地满足其预期用途。

  • 会话 Cookie 是临时的,只在用户会话期间存在。它们存储即时信息,如购物车中的物品:
js 复制代码
// 示例:设置一个会话 cookie,即不设置到期时间
document.cookie = "sessionID=abc123; path=/";
  • 持久 Cookie 有一定有效期,在用户设备上保留的时间更长。它适用于 "记住我" 等功能:
js 复制代码
// 示例:设置具有到期时间的持久 cookie
document.cookie =
  "username=JohnDoe; expires=Fri, 31 Dec 2023 23:59:59 GMT; path=/";
  • 用户身份认证。当我们成功登录后,会话令牌或 JWT(JSON Web Token)通常会存储在 cookie 中:
js 复制代码
document.cookie = "token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...; path=/";
  • 用户偏好。Cookie 通常会存储用户的偏好,如主题选择或语言设置,以获得更个性化的体验。
js 复制代码
// 示例:设置个用户偏好的 cookie
document.cookie = "theme=dark; path=/";

Session

定义和目的

Session 代表一个带有逻辑的服务器端实体,用于存储用户访问期间的特定数据。

Session 与 cookie 密切相关,但在存储方面有所不同;会话标识符通常存储在客户端 cookie 中。(更多用户数据存储在服务器上)。

服务器端会话与客户端会话

  • 服务器端会话涉及在服务器上存储会话数据。Express.js 等框架使用服务器端会话来管理用户状态:
js 复制代码
// 使用 express-session 中间件
const express = require('express');
const session = require('express-session');
const app = express();

app.use(session({
  secret: 'your-secret-key',
  resave: false,
  saveUninitialized: true,
}));
  • 客户端会话。客户端会话可确保分布式下无需跨节点复制、认证会话或查询数据存储。虽然"客户端会话"可能指的是客户端上的会话存储信息,但通常是指使用 cookie 来存储会话标识符:

了解 cookie 和会 session 的细微差别有助于构建交互式 web 应用。

下一节将探讨 React 应用中 cookie 和 session 的实际实现。

使用 document.cookie API

在 React 中使用 cookie 的最基本方法是通过 document.cookie API,它能设置、获取和删除 cookie。

  • 设置 cookie:
js 复制代码
// 设置 cookie 函数
const setCookie = (name, value, days) => {
 const expirationDate = new Date();
 expirationDate.setDate(expirationDate.getDate() + days);

 document.cookie = `${name}=${value}; expires=${expirationDate.toUTCString()}; path=/`;
};

// 示例: 设置个 username cookie,7 天后过期
setCookie("username", "john_doe", 7);
  • 获取 cookie
js 复制代码
// 通过 name 获取 cookie 的函数
const getCookie = (name) => {
 const cookies = document.cookie
   .split("; ")
   .find((row) => row.startsWith(`${name}=`));

 return cookies ? cookies.split("=")[1] : null;
};

// 示例: 获取 username 的 cookie 值
const username = getCookie("username");
  • 删除 cookie
js 复制代码
// 通过 nama 删除 cookie 的函数
const deleteCookie = (name) => {
 document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;
};

// 示例:删除 username cookie
deleteCookie("username");

封装与 cookie 相关的自定义 React Hook,使其可跨组件复用:

js 复制代码
// useCookie.js
import { useState, useEffect } from "react";

const useCookie = (cookieName) => {
  const [cookieValue, setCookieValue] = useState("");

  useEffect(() => {
    const cookie = document.cookie
      .split("; ")
      .find((row) => row.startsWith(`${cookieName}=`));

    setCookieValue(cookie ? cookie.split("=")[1] : "");
  }, [cookieName]);

  const setCookie = (value, expirationDate) => {
    document.cookie = `${cookieName}=${value}; expires=${expirationDate.toUTCString()}; path=/`;
  };

  const deleteCookie = () => {
    document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;
  };

  return [cookieValue, setCookie, deleteCookie];
};

// 在 React 组件中使用
const [username, setUsername, deleteUsername] = useCookie("username");

这个自定义 Hook useCookie 会返回 cookie 的当前值、一个用于设置新值的函数和一个用于删除 cookie 的函数。

使用第三方库

第三方库(如 js-cookie)可简化 React 应用中的 cookie 管理。

  • 安装库
js 复制代码
npm install js-cookie
  • 在 React 组件中的使用
js 复制代码
// React 组件中使用 js-cookie 的示例
import React, { useEffect } from "react";
import Cookies from "js-cookie";

const MyComponent = () => {
 // Set a cookie
 useEffect(() => {
   Cookies.set("user_token", "abc123", { expires: 7, path: "/" });
 }, []);

 // Get a cookie
 const userToken = Cookies.get("user_token");

 // Delete a cookie
 const logout = () => {
   Cookies.remove("user_token");
   // Additional logout logic...
 };

 return (
   <div>
     <p>User Token: {userToken}</p>
     <button onClick={logout}>Logout</button>
   </div>
 );
};

export default MyComponent;

使用第三方库(如 js-cookie)可为 cookie 管理提供简洁的 API。

了解这些不同的方法有助于我们选择最适合方式管理 cookie。

实现 Session 会话

在 React 应用中,Seesion 会话在服务端存储,而会话标识符则通过 cookie 存在客户端。

实现 session 的方式有:

  • 服务端 session 会话
  • 基于令牌(token)的认证

服务端 session 会话

服务端会话涉及在服务器上存储会话数据。在 React 中,这意味着要使用 Express.js 等服务端框架和会话管理中间件。

  • 使用 Express.js 和 express-session 初始化

首先,安装所需的包:

js 复制代码
npm install express express-session

现在,配置 Express:

js 复制代码
// server.js
const express = require("express");
const session = require("express-session");
const app = express();

app.use(
 session({
   secret: "your-secret-key",
   resave: false,
   saveUninitialized: true,
 })
);

// ...其他的 Express 配置和路由

secret 为存储在 cookie 的会话 ID

  • 在路由中使用会话

配置会话后,我们就可以在路由中使用它们:

js 复制代码
// server.js
app.post("/login", (req, res) => {
 // 假设认证成功
 req.session.user = { id: 1, username: "john_doe" };
 res.send("Login successful!");
});

app.get("/profile", (req, res) => {
 // 从 session 获取用户数据
 const user = req.session.user;
 if (user) {
   res.json({ message: "Welcome to your profile!", user });
 } else {
   res.status(401).json({ message: "Unauthorized" });
 }
});

登录成功后,用户信息会存储在会话中。随后对 /profile 路由的请求就可以访问这些信息。

基于令牌(token)的认证

基于令牌的身份认证是在现代 React 应用中管理会话的一种方法。它包括在认证成功后在服务器上生成令牌,将其发送到客户端,客户端在随后的请求中会在请求头中带上令牌。

  • 生成和发送令牌

服务端方面:

js 复制代码
// server.js
const jwt = require("jsonwebtoken");

app.post("/login", (req, res) => {
 // 假设认证成功
 const user = { id: 1, username: "john_doe" };
 const token = jwt.sign(user, "your-secret-key", { expiresIn: "1h" });
 res.json({ token });
});

服务端会生成一个 JWT(JSON Web Token)并发送给客户端。

  • 在请求中带上令牌

在客户端(React):

js 复制代码
// AuthProvider.js - 使用 React Context 做状态管理的示例
import React, { createContext, useContext, useReducer } from "react";

const AuthContext = createContext();

const authReducer = (state, action) => {
 switch (action.type) {
   case "LOGIN":
     return { ...state, isAuthenticated: true, token: action.token };
   case "LOGOUT":
     return { ...state, isAuthenticated: false, token: null };
   default:
     return state;
 }
};

const AuthProvider = ({ children }) => {
 const [state, dispatch] = useReducer(authReducer, {
   isAuthenticated: false,
   token: null,
 });

 const login = (token) => dispatch({ type: "LOGIN", token });
 const logout = () => dispatch({ type: "LOGOUT" });

 return (
   <AuthContext.Provider value={{ state, login, logout }}>
     {children}
   </AuthContext.Provider>
 );
};

const useAuth = () => {
 const context = useContext(AuthContext);
 if (!context) {
   throw new Error("useAuth must be used within an AuthProvider");
 }
 return context;
};

export { AuthProvider, useAuth };

以上使用 React Context 管理身份认证状态。登录函数会根据收到的令牌更新状态。

  • 在请求中使用令牌

有了令牌,我们就可以在请求的 headers 中加入令牌:

js 复制代码
// api.js - 请求需要认证信息的 API 接口工具方法
import axios from "axios";
import { useAuth } from "./AuthProvider";

const api = axios.create({
 baseURL: "https://your-api-url.com",
});

api.interceptors.request.use((config) => {
 const { state } = useAuth();

 if (state.isAuthenticated) {
   config.headers.Authorization = `Bearer ${state.token}`;
 }

 return config;
});

export default api;

使用 Axios 发起请求时,令牌会自动出现在请求头中。

这两种方法都能帮助我们有效管理会话,提供安全、无缝的体验。

在 React 应用中处理 session 和 cookie 对于构建安全、用户友好和性能卓越的 web 应用至关重要。

为确保 React 应用正常运行,请执行以下操作。

在适用情况下,Cookie 应始终包含 HttpOnlySecure 标志。

  • HttpOnly。该标记可防止通过 JavaScript 或任何其他恶意代码对 cookie 进行攻击,从而降低跨站脚本 (XSS) 攻击的风险。它可确保只有服务器才能访问 cookie:
js 复制代码
document.cookie = "sessionID=abc123; HttpOnly; path=/";
  • Secure。该标记可确保 cookie 只通过安全的加密连接(HTTPS)发送。它能降低被恶意用户截获的风险:
js 复制代码
document.cookie = "sessionID=abc123; Secure; path=/";

实现会话到期和令牌刷新

为提高安全性,需要实现会话过期和令牌刷新功能。定期刷新令牌或设置会话到期时间有助于降低未经授权访问的风险。

  • 刷新令牌。刷新身份认证令牌,确保用户保持身份认证状态,它适用于用户会话时间较长的应用。
  • 会话到期。设置合理的会话到期时间,限制用户会话的持续时间,这有助于防止会话劫持。
js 复制代码
// server.js
const express = require("express");
const jwt = require("jsonwebtoken");

const app = express();
app.use(express.json());

const secretKey = "your-secret-key";

// 生成具有延长过期时间的新令牌的函数
const generateToken = (user) => {
  return jwt.sign(user, secretKey, { expiresIn: "15m" });
};

app.post("/login", (req, res) => {
  // 验证用户凭据(为简单起见,此处不包括)

  // 假设成功认证
  const user = { id: 1, username: "john_doe" };
  const token = generateToken(user);

  res.json({ token });
});

app.post("/refresh-token", (req, res) => {
  const refreshToken = req.body.refreshToken;

  // 验证刷新令牌(为简单起见,此处不包括)

  // 如果刷新令牌有效,则生成新的访问令牌
  const user = decodeRefreshToken(refreshToken); // 解码刷新令牌的函数
  const newToken = generateToken(user);

  res.json({ token: newToken });
});

app.listen(3001, () => {
  console.log("Server is running on port 3001");
});

/login 接口会在认证成功后返回一个初始 JWT 令牌。/refresh-token 接口使用刷新令牌生成新的访问令牌。

加密敏感数据

避免在 cookie 或会话中直接存储敏感信息。为在不可避免的情况下保存敏感数据,应在存储前对其进行加密。加密可增加一层额外的安全性,即使恶意用户截获数据,也很难获取敏感信息:

js 复制代码
// 示例: 在 cookie 中加密存储敏感数据
const sensitiveData = encrypt(data);
document.cookie = `sensitiveData=${sensitiveData}; Secure; HttpOnly; path=/`;

使用 SameSite 属性

SameSite 属性可以指定跨站请求中发送 cookie 的规则,有助于防范跨站请求伪造(CSRF)攻击。

  • Strict。当前网页的 URL 与请求目标一致时发送 cookie,防止第三方网站代表用户发出请求,比如第三方地址的图片。
js 复制代码
document.cookie =
  "sessionID=abc123; Secure; HttpOnly; SameSite=Strict; path=/";
  • Lax。允许我们在顶级导航(如点击链接)时发送 cookie,但不允许在第三方网站发起的跨站 POST 请求中发送 cookie:
js 复制代码
document.cookie = "sessionID=abc123; Secure; HttpOnly; SameSite=Lax; path=/";

将身份验证与应用状态分开

避免在 cookie 或会话中存储整个应用状态。将身份验证数据与其他应用相关的状态分开,以保持清晰度,并将暴露敏感信息的风险降至最低:

js 复制代码
// 把认证 token 存储在一个单独的 cookie 项中
document.cookie = "authToken=xyz789; Secure; HttpOnly; path=/";

考虑使用成熟的第三方库进行 cookie 管理。js-cookie 等库提供了简洁方便的 API,抽象了 document.cookie API,降低了使用复杂度:

js 复制代码
// 使用 js-cookie 设置、获取、删除 cookie
import Cookies from "js-cookie";

Cookies.set("username", "john_doe", { expires: 7, path: "/" });
const username = Cookies.get("username");
Cookies.remove("username");

定期更新依赖项

及时更新第三方库和框架,以便从安全补丁和改进中获益。定期更新依赖关系可确保我们的应用不易受到已知漏洞的影响。

安全检测措施

定期对应用进行安全审计和测试。其中包括对 XSS 和 CSRF 等常见漏洞的测试。考虑使用内容安全策略 (CSP) 等安全工具和实践来降低安全风险。

总结

Cookie 和 session 是构建安全高效的 React 应用的有用工具。它们可用于管理用户身份认证、保留用户偏好或实现有状态的交互。

通过遵循最佳实践和使用成熟的库,我们可以创建稳健可靠的应用,给用户提供无缝体验,同时能考虑到安全性。

如果你喜欢这篇 React 文章,请查看 SitePoint 提供的这些精彩资源:

相关推荐
01传说7 分钟前
vue3 配置安装 pnpm 报错 已解决
java·前端·vue.js·前端框架·npm·node.js
Misha韩14 分钟前
React Native 一些API详解
react native·react.js
小李飞飞砖14 分钟前
React Native 组件间通信方式详解
javascript·react native·react.js
小李飞飞砖15 分钟前
React Native 状态管理方案全面对比
javascript·react native·react.js
烛阴1 小时前
Python装饰器解除:如何让被装饰的函数重获自由?
前端·python
千鼎数字孪生-可视化2 小时前
Web技术栈重塑HMI开发:HTML5+WebGL的轻量化实践路径
前端·html5·webgl
凌辰揽月2 小时前
7月10号总结 (1)
前端·css·css3
天天扭码2 小时前
很全面的前端面试——CSS篇(上)
前端·css·面试
EndingCoder2 小时前
搜索算法在前端的实践
前端·算法·性能优化·状态模式·搜索算法
sunbyte2 小时前
50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | DoubleVerticalSlider(双垂直滑块)
前端·javascript·css·vue.js·vue