跳到主要内容

身份验证与安全性

在 Express.js 应用程序中,身份验证与会话管理是确保用户数据安全和访问控制的核心功能。以下是三种常见的身份验证机制:CookieSessionJWT (JSON Web Token) 以及路由保护的详细介绍。

Session 通过在服务器端保存会话数据,并在客户端通过 Cookie 存储 Session ID 来实现。客户端的每个请求都会包含这个 ID,服务器根据此 ID 查找并验证会话数据。

安装依赖

npm install express-session cookie-parser
  • express-session:用于管理服务器端的会话。
  • cookie-parser:用于解析请求中的 Cookie。

基本实现

以下是一个使用 Session 实现用户会话管理的例子:

import express from 'express';
import session from 'express-session';
import cookieParser from 'cookie-parser';

const app = express();

// 使用 cookie-parser 解析请求中的 Cookie
app.use(cookieParser());

// 使用 express-session 管理 Session
app.use(
session({
secret: 'your-secret-key', // 用于加密 Session ID 的密钥
resave: false, // 如果 Session 没有修改,是否强制保存
saveUninitialized: true, // 是否保存未初始化的会话
cookie: { secure: false }, // 如果使用 HTTPS,secure 应设为 true
}),
);

// 测试会话存储
app.get('/', (req, res) => {
if (req.session.views) {
req.session.views++; // 增加访问次数
res.send(`You have visited this page ${req.session.views} times`);
} else {
req.session.views = 1; // 初始化访问次数
res.send(
'Welcome to the session demo! Refresh to see the view count increase.',
);
}
});

app.listen(1234, () => {
console.log('Server is running on http://localhost:1234');
});
  1. 用户首次访问时,服务器生成一个唯一的 Session ID 并存储在 Cookie 中。
  2. 后续的每次请求都会带上这个 Cookie,服务器根据 Session ID 找到对应的会话数据。

登入和登出

// 用户登录
app.post('/login', (req, res) => {
const { username } = req.body;
req.session.username = username; // 将用户信息存储在 Session 中
res.send(`Logged in as ${username}`);
});

// 用户登出
app.post('/logout', (req, res) => {
req.session.destroy(err => {
if (err) {
return res.status(500).send('Logout failed');
}
res.send('Logged out successfully');
});
});

使用 JWT 进行身份验证

JWT (JSON Web Token) 是一种无状态的身份验证方式,用户信息直接存储在 Token 中,客户端持有 Token,服务器通过验证 Token 来识别用户。

安装依赖

npm install jsonwebtoken bcryptjs
  • jsonwebtoken:生成和验证 JWT。
  • bcryptjs:用于加密和验证用户密码。

JWT 身份验证流程

  1. 用户注册:用户输入用户名和密码,密码通过 bcrypt 加密后存储。
  2. 用户登录:用户提供用户名和密码,服务器验证后生成 JWT 并返回给客户端。
  3. 验证请求:每次客户端请求时,携带 JWT,服务器验证该 Token 并允许访问受保护资源。

实现用户注册和登录

import express from 'express';
import jwt from 'jsonwebtoken';
import bcrypt from 'bcryptjs';

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

const users: { id: number; username: string; password: string }[] = [];
const secretKey = 'your-secret-key'; // JWT 密钥

// 用户注册
app.post('/register', async (req, res) => {
const { username, password } = req.body;
const hashedPassword = await bcrypt.hash(password, 10); // 密码加密
const newUser = { id: users.length + 1, username, password: hashedPassword };
users.push(newUser);
res.status(201).json({ message: 'User registered successfully' });
});

// 用户登录并生成 JWT
app.post('/login', async (req, res) => {
const { username, password } = req.body;
const user = users.find(u => u.username === username);

if (!user || !(await bcrypt.compare(password, user.password))) {
return res.status(401).json({ message: 'Invalid credentials' });
}

// 生成 JWT
const token = jwt.sign({ id: user.id, username: user.username }, secretKey, { expiresIn: '1h' });
res.json({ token });
});

验证 JWT 的中间件

通过中间件来验证用户请求中的 JWT。

function authenticateToken(req: any, res: any, next: any) {
const token = req.headers['authorization'];
if (!token) return res.sendStatus(401); // 未提供 Token

jwt.verify(token.split(' ')[1], secretKey, (err: any, user: any) => {
if (err) return res.sendStatus(403); // Token 无效或过期
req.user = user; // 将解码的用户信息存储到 req 中
next();
});
}

受保护的路由

只有已登录并携带有效 JWT 的用户才能访问受保护的路由。

app.get('/protected', authenticateToken, (req, res) => {
res.json({ message: 'This is a protected route', user: req.user });
});

路由保护

通过使用中间件,可以保护特定路由,使得只有通过身份验证的用户才能访问。

在上述 JWT 示例中,authenticateToken 函数就是一个用来保护路由的中间件。所有受保护的路由都需要经过这个中间件的验证。

app.get('/user/profile', authenticateToken, (req, res) => {
// 用户信息只有在验证通过后才能访问
res.json({ profile: req.user });
});

安全性

在使用 Express.js 开发 web 应用时,安全性是非常重要的一个方面。常见的安全攻击包括跨站脚本攻击(XSS)、跨站请求伪造(CSRF)和 SQL 注入。为了防御这些攻击,我们可以采取多种措施,如数据过滤、验证、使用第三方安全库等。

以下是防御这些常见安全攻击的详细说明,以及如何使用 helmet 来增强 Express 应用的安全性。

防御 XSS(跨站脚本攻击)

什么是 XSS?

XSS 攻击是指攻击者通过向网页注入恶意脚本,使用户在访问页面时执行这些脚本。它通常通过不安全的数据输入或输出来实现。

如何防御 XSS?

  • 输入过滤与输出编码:永远不要信任用户输入,在处理用户输入的数据时应进行过滤和清理。输出到 HTML 时,应对数据进行编码。
  • 使用库来防止 XSS:例如 xss-cleanDOMPurify 等库可以帮助你过滤不安全的 HTML 内容。

安装 xss-clean 库:

npm install xss-clean

在 Express 中应用 xss-clean

import express from 'express';
import xssClean from 'xss-clean';

const app = express();

// 防御 XSS 攻击
app.use(xssClean());

app.post('/submit', (req, res) => {
// 假设用户提交的数据被存储或呈现给其他用户
res.send(`Received input: ${req.body.input}`);
});

app.listen(1234, () => {
console.log('Server is running on http://localhost:1234');
});

防御 CSRF(跨站请求伪造)

什么是 CSRF?

CSRF 攻击是指攻击者通过欺骗用户的身份认证信息,让用户在不知情的情况下执行恶意操作,如发起转账请求、修改密码等。

如何防御 CSRF?

使用 CSRF 令牌:每个请求附带一个唯一的 CSRF 令牌,只有服务器生成并验证的令牌才能通过验证。

手动生成和验证 CSRF Token

你可以在每个请求中生成并验证 CSRF Token,通过如下步骤:

  • 服务器在用户首次请求页面时生成一个随机的 CSRF Token,并将其存储在用户的 session 或 cookie 中,同时将该 Token 发送到前端(通常通过隐藏表单字段或自定义 HTTP 头)。
  • 用户提交表单时,Token 会随着请求发送到服务器。
  • 服务器验证请求中的 Token 和 session/cookie 中的 Token 是否匹配。
示例
import express from 'express';
const app = express();

const crypto = require('crypto');

// 使用 cookie-parser 中间件
const cookieParser = require('cookie-parser');
app.use(cookieParser());
// 解析body数据
app.use(express.json());

// 生成 CSRF Token
function generateCsrfToken() {
return crypto.randomBytes(16).toString('hex');
}

// Middleware: 生成 CSRF Token 并添加到 cookie 中
app.use((req, res, next) => {
if (!req.cookies.csrfToken) {
const csrfToken = generateCsrfToken();
res.cookie('csrfToken', csrfToken, { httpOnly: true });
}
next();
});

// Middleware: 验证 CSRF Token
function csrfProtection(req, res, next) {
const tokenFromRequest = req.body.csrfToken || req.headers['x-csrf-token'];
const tokenFromCookie = req.cookies.csrfToken;

if (!tokenFromRequest || tokenFromRequest !== tokenFromCookie) {
return res.status(403).send('Invalid CSRF token');
}
next();
}

app.get('/', (req, res) => {
res.send('Hello World!');
});

// 使用保护中间件
app.post('/submit', csrfProtection, (req, res) => {
res.send('Form submitted successfully');
});

app.listen(1234, () => {
console.log('Server is running on http://localhost:1234');
});

防御 SQL 注入

什么是 SQL 注入?

SQL 注入攻击是指攻击者通过输入恶意 SQL 代码,从而操纵 SQL 查询,获取或修改数据库中的敏感数据。

如何防御 SQL 注入?

  • 使用参数化查询:永远不要直接在 SQL 查询中拼接用户输入,应该使用参数化查询或 ORM 工具来避免 SQL 注入。
  • 使用 ORM:如 Sequelize 或 TypeORM,这些工具会自动处理参数化查询,降低 SQL 注入风险。

使用 helmet 增强安全性

helmet 是一个非常流行的中间件,它通过设置各种 HTTP 头来帮助防止常见的安全漏洞。它可以自动防止一些常见的攻击,如点击劫持、XSS 和内容注入等。

安装 helmet:

npm install helmet

在 Express 中应用 helmet

import express from 'express';
import helmet from 'helmet';

const app = express();

// 使用 helmet 增强应用安全性
app.use(helmet());

app.get('/', (req, res) => {
res.send('Helmet is securing your app!');
});

app.listen(3000, () => {
console.log('Server is running on http://localhost:3000');
});

helmet 中的部分功能:

  • helmet.hidePoweredBy():隐藏 X-Powered-By 头信息,防止暴露服务器框架类型(默认开启)。
  • helmet.xssFilter():启用浏览器的 XSS 过滤器。
  • helmet.frameguard():防止点击劫持攻击(Clickjacking),默认设置为 deny。
  • helmet.hsts():强制使用 HTTPS,保护用户免受 SSL 剥离攻击。
示例
app.use(helmet.xssFilter());

你可以通过传入配置对象来自定义 helmet 的某些功能:

app.use(
helmet({
contentSecurityPolicy: false, // 禁用内容安全策略
frameguard: { action: 'sameorigin' }, // 允许同源的页面在 iframe 中展示
}),
);