[译] 在 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 提供的这些精彩资源:

相关推荐
gqkmiss22 分钟前
Chrome 浏览器 131 版本开发者工具(DevTools)更新内容
前端·chrome·浏览器·chrome devtools
Summer不秃27 分钟前
Flutter之使用mqtt进行连接和信息传输的使用案例
前端·flutter
旭日猎鹰32 分钟前
Flutter踩坑记录(二)-- GestureDetector+Expanded点击无效果
前端·javascript·flutter
Viktor_Ye38 分钟前
高效集成易快报与金蝶应付单的方案
java·前端·数据库
hummhumm40 分钟前
第 25 章 - Golang 项目结构
java·开发语言·前端·后端·python·elasticsearch·golang
乐闻x1 小时前
Vue.js 性能优化指南:掌握 keep-alive 的使用技巧
前端·vue.js·性能优化
一条晒干的咸魚1 小时前
【Web前端】创建我的第一个 Web 表单
服务器·前端·javascript·json·对象·表单
花海少爷1 小时前
第十章 JavaScript的应用课后习题
开发语言·javascript·ecmascript
Amd7941 小时前
Nuxt.js 应用中的 webpack:compiled 事件钩子
前端·webpack·开发·编译·nuxt.js·事件·钩子
生椰拿铁You2 小时前
09 —— Webpack搭建开发环境
前端·webpack·node.js