# 安全性实践

URL: https://caijiao.org/express/chapter10/4-security
Source: docs/express/chapter10/4-security.md
Description: 本章节将介绍如何确保 Express 应用的安全性。我们将探讨常见的安全风险以及如何通过使用适当的安全措施来防止这些威胁，包括防止跨站脚本攻击（XSS）、跨站请求伪造（CSRF）、SQL 注入等安全问题的最佳实践。

在开发生产环境的 Express 应用时，安全性是一个至关重要的考虑因素。无论应用是面向用户还是 API，存在的安全漏洞都可能导致数据泄露、服务中断或恶意攻击。因此，采取适当的安全措施是必不可少的。本章节将介绍一些常见的安全风险，并提供防护措施和最佳实践。

## 1. 防止跨站脚本攻击 (XSS)

跨站脚本攻击（XSS）是一种恶意攻击，攻击者通过在网页中注入恶意 JavaScript 代码，来窃取用户数据或执行恶意操作。为防止 XSS 攻击，我们应当始终对用户输入进行验证和过滤。

### 1.1 使用模板引擎自动转义

大多数模板引擎，如 EJS 和 Pug，都自动转义 HTML 标签和 JavaScript 代码，这可以有效防止 XSS 攻击。因此，在渲染用户生成的内容时，确保使用模板引擎的安全功能。

例如，使用 EJS：

```js
<p><%= userInput %></p>
````

EJS 会自动转义 `userInput`，防止其中包含的 HTML 或 JavaScript 被执行。

### 1.2 手动转义输入

如果不使用模板引擎自动转义，可以手动转义用户输入的内容：

```js
const escapeHtml = require('escape-html');

app.post('/submit', (req, res) => {
  const sanitizedInput = escapeHtml(req.body.input);
  res.send(sanitizedInput);
});
```

`escape-html` 库会将 HTML 特殊字符转义为实体，防止恶意代码的注入。

### 1.3 使用 Content Security Policy (CSP)

Content Security Policy（CSP）是一种防止 XSS 攻击的强大机制。通过在 HTTP 头中设置 CSP，可以限制网页加载的内容来源，避免不受信任的脚本执行。

在 Express 中使用 CSP：

```js
const helmet = require('helmet');
app.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"],
    scriptSrc: ["'self'", "'unsafe-inline'"],
  }
}));
```

## 2. 防止跨站请求伪造 (CSRF)

跨站请求伪造（CSRF）攻击通过诱使用户在不知情的情况下执行恶意请求，从而篡改数据或执行不安全操作。防止 CSRF 攻击的常用方法是为每个请求生成一个唯一的令牌，并验证该令牌。

### 2.1 使用 CSRF 令牌

在 Express 中，可以使用 `csurf` 库来生成和验证 CSRF 令牌。

安装 `csurf`：

```bash
npm install csurf
```

在应用中使用 CSRF 保护：

```js
const csrf = require('csurf');
const csrfProtection = csrf({ cookie: true });

app.use(csrfProtection);

app.get('/form', (req, res) => {
  res.render('form', { csrfToken: req.csrfToken() });
});

app.post('/submit', (req, res) => {
  res.send('Data is being processed');
});
```

在表单中添加 CSRF 令牌：

```html
<form method="POST" action="/submit">
  <input type="hidden" name="_csrf" value="<%= csrfToken %>">
  <button type="submit">Submit</button>
</form>
```

## 3. 防止 SQL 注入

SQL 注入攻击是一种通过在 SQL 查询中插入恶意代码来篡改数据库的攻击方式。为了避免 SQL 注入，我们应使用预处理语句（Prepared Statements）和 ORM 工具。

### 3.1 使用 ORM 进行数据库操作

使用 ORM（如 Mongoose、Sequelize 或 TypeORM）能够有效防止 SQL 注入。ORM 库会自动使用参数化查询，从而避免直接拼接用户输入的 SQL 语句。

例如，使用 Mongoose：

```js
const User = require('./models/User');

app.get('/user', async (req, res) => {
  const userId = req.query.userId;
  const user = await User.findById(userId);
  res.json(user);
});
```

### 3.2 使用参数化查询

如果你使用原生 SQL 查询，应确保使用参数化查询或预处理语句来避免 SQL 注入。例如，使用 `pg` 库时：

```js
const { Client } = require('pg');
const client = new Client();

app.get('/user', async (req, res) => {
  const userId = req.query.userId;
  const result = await client.query('SELECT * FROM users WHERE id = $1', [userId]);
  res.json(result.rows[0]);
});
```

## 4. 设置 HTTP 安全头

HTTP 安全头可以防止许多常见的 Web 安全攻击，如 XSS、点击劫持等。

### 4.1 使用 Helmet 设置安全头

[Helmet](https://www.npmjs.com/package/helmet) 是一个中间件，它帮助我们设置一些常见的 HTTP 安全头来保护应用免受各种攻击。

安装 `helmet`：

```bash
npm install helmet
```

在 Express 应用中使用 `helmet`：

```js
const helmet = require('helmet');
app.use(helmet());
```

`helmet` 默认启用一系列安全头配置，如：

* `X-Content-Type-Options: nosniff`：防止浏览器猜测文件类型。
* `X-XSS-Protection: 1; mode=block`：启用浏览器的 XSS 保护。
* `Strict-Transport-Security`：启用 HTTP 严格传输安全，强制客户端使用 HTTPS。

### 4.2 配置 HSTS

HTTP 严格传输安全（HSTS）强制客户端在一段时间内只通过 HTTPS 访问网站。你可以在应用中设置 HSTS 头：

```js
app.use(helmet.hsts({
  maxAge: 31536000,  // 设置为 1 年
  includeSubDomains: true, // 对所有子域启用 HSTS
  preload: true  // 允许浏览器将域名预加载到 HSTS 列表中
}));
```

## 5. 加强身份验证与授权

确保应用的身份验证和授权机制安全可靠。常见的安全风险包括弱密码、会话劫持等。

### 5.1 使用强密码

确保用户使用强密码进行注册和登录。可以使用 `bcrypt` 或 `argon2` 等密码哈希库来加密密码。

安装 `bcrypt`：

```bash
npm install bcrypt
```

示例代码：

```js
const bcrypt = require('bcrypt');

app.post('/signup', async (req, res) => {
  const hashedPassword = await bcrypt.hash(req.body.password, 10);
  // 将 hashedPassword 存储到数据库中
});
```

### 5.2 使用 HTTPS

确保所有敏感数据（如密码、用户信息等）通过 HTTPS 进行加密传输。在生产环境中始终使用 HTTPS 来防止中间人攻击。

### 5.3 使用 JWT 验证

JSON Web Token（JWT）是一种用于无状态身份验证的方式。JWT 可以有效避免会话劫持，因为它是基于令牌的身份验证，且数据是加密的。

```js
const jwt = require('jsonwebtoken');
const secretKey = 'your-secret-key';

// 用户登录后生成 JWT
const token = jwt.sign({ userId: user._id }, secretKey, { expiresIn: '1h' });

// 验证 JWT
app.use((req, res, next) => {
  const token = req.headers['authorization'];
  if (!token) {
    return res.status(403).send('Forbidden');
  }

  jwt.verify(token, secretKey, (err, decoded) => {
    if (err) {
      return res.status(403).send('Forbidden');
    }
    req.user = decoded;
    next();
  });
});
```

## 6. 总结

本章节介绍了多种保护 Express 应用的安全措施：

1. **防止 XSS**：使用模板引擎的自动转义功能，或手动转义用户输入。
2. **防止 CSRF**：使用 CSRF 令牌来保护表单免受伪造请求。
3. **防止 SQL 注入**：使用 ORM 或参数化查询来避免 SQL 注入。
4. **设置安全 HTTP 头**：使用 Helmet 设置常见的 HTTP 安全头，启用 HTTPS 和 HSTS。
5. **加强身份验证与授权**：使用强密码、JWT 验证、HTTPS 等措施保护用户数据和会话安全。

通过采取这些安全措施，可以有效地防止常见的 Web 安全威胁，确保 Express 应用在生产环境中的安全性。
