在本文中,我们将探讨 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 的基础知识
了解 cookie 和 session 的基础知识是开发用户交互友好的 web 应用的基础。
本节将深入探讨 cookie 和 session 的概念,探索它们的类型、生命周期和典型使用场景。
Cookie
Cookie 主要是维护客户端和服务器之间状态数据,通过请求来维护。通过 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=/";
React 中 cookie 的使用案例
- 用户身份认证。当我们成功登录后,会话令牌或 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 封装自定义 Hook
封装与 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 的最佳实践
在 React 应用中处理 session 和 cookie 对于构建安全、用户友好和性能卓越的 web 应用至关重要。
为确保 React 应用正常运行,请执行以下操作。
使用 HttpOnly 和 secure 标记确保 cookie 的安全
在适用情况下,Cookie 应始终包含 HttpOnly
和 Secure
标志。
- 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 管理
考虑使用成熟的第三方库进行 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 提供的这些精彩资源: