MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Node.js Express 安全性配置与防护措施

2023-11-247.0k 阅读

Node.js Express 安全性配置与防护措施

输入验证与过滤

在 Web 应用开发中,输入验证与过滤是第一道防线,能够有效防止恶意用户通过输入非法数据来攻击应用。

  1. 基本输入验证 在 Express 应用中,我们可以使用第三方库,如 joi 来进行输入验证。joi 是一个功能强大的 schema 描述语言和数据验证器。

首先安装 joi

npm install joi

假设我们有一个处理用户注册的路由,需要验证用户名、邮箱和密码:

const express = require('express');
const Joi = require('joi');
const app = express();

const userSchema = Joi.object({
  username: Joi.string().min(3).max(30).required(),
  email: Joi.string().email().required(),
  password: Joi.string().pattern(new RegExp('^[a-zA-Z0-9]{3,30}$')).required()
});

app.post('/register', (req, res) => {
  const { error } = userSchema.validate(req.body);
  if (error) {
    return res.status(400).send(error.details[0].message);
  }
  // 处理注册逻辑
  res.send('Registration successful');
});

const port = 3000;
app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

在上述代码中,userSchema 定义了用户名、邮箱和密码的验证规则。validate 方法对请求体进行验证,如果验证失败,返回错误信息给客户端。

  1. SQL 注入过滤 如果应用使用 SQL 数据库,SQL 注入是一个严重的安全威胁。例如,在使用 mysql 库连接 MySQL 数据库时,使用参数化查询来防止 SQL 注入。

安装 mysql

npm install mysql

假设我们有一个查询用户信息的路由:

const express = require('express');
const mysql = require('mysql');
const app = express();

const connection = mysql.createConnection({
  host: 'localhost',
  user: 'root',
  password: 'password',
  database: 'test'
});

connection.connect();

app.get('/user/:id', (req, res) => {
  const query = 'SELECT * FROM users WHERE id =?';
  connection.query(query, [req.params.id], (error, results, fields) => {
    if (error) throw error;
    res.send(results);
  });
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

在上述代码中,? 是参数占位符,req.params.id 作为参数传入 connection.query 方法,这样可以防止恶意用户通过构造恶意的 id 参数来执行非法的 SQL 语句。

  1. XSS 过滤 跨站脚本攻击(XSS)是攻击者在网页中注入恶意脚本的攻击方式。我们可以使用 DOMPurify 库来过滤掉恶意的 HTML 和 JavaScript 代码。

安装 DOMPurify

npm install dompurify

假设我们有一个处理用户评论的路由:

const express = require('express');
const DOMPurify = require('dompurify');
const app = express();

app.post('/comment', (req, res) => {
  const cleanComment = DOMPurify.sanitize(req.body.comment);
  // 保存过滤后的评论到数据库
  res.send('Comment saved successfully');
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

在上述代码中,DOMPurify.sanitize 方法对用户输入的评论进行过滤,确保不包含恶意脚本。

安全 HTTP 头配置

HTTP 头可以用来增强应用的安全性,设置一些安全相关的头信息可以有效防止多种攻击。

  1. Content - Security - Policy(CSP) CSP 用于限制网页可以加载的资源来源,防止 XSS 攻击。在 Express 中,我们可以使用 helmet 库来设置 CSP 头。

安装 helmet

npm install helmet

配置 CSP:

const express = require('express');
const helmet = require('helmet');
const app = express();

app.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"],
    scriptSrc: ["'self'", 'https://code.jquery.com'],
    styleSrc: ["'self'", 'https://maxcdn.bootstrapcdn.com']
  }
}));

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

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

在上述代码中,defaultSrc 设置为 'self',表示默认只允许从当前域加载资源。scriptSrc 除了当前域外,还允许从 https://code.jquery.com 加载脚本,styleSrc 除了当前域外,允许从 https://maxcdn.bootstrapcdn.com 加载样式。

  1. X - Frame - Options X - Frame - Options 头用于防止点击劫持攻击,即防止页面被嵌入到其他页面的 <frame><iframe><object> 中。

使用 helmet 设置 X - Frame - Options

const express = require('express');
const helmet = require('helmet');
const app = express();

app.use(helmet.frameguard({ action: 'deny' }));

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

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

上述代码中,action: 'deny' 表示不允许页面被任何其他页面嵌入。

  1. X - XSS - Protection X - XSS - Protection 头用于启用浏览器内置的 XSS 过滤器。虽然现代浏览器已经有了较好的 XSS 防护机制,但设置这个头可以进一步增强安全性。

使用 helmet 设置 X - XSS - Protection

const express = require('express');
const helmet = require('helmet');
const app = express();

app.use(helmet.xssFilter());

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

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

身份验证与授权

  1. 基于 Token 的身份验证 基于 Token 的身份验证是一种常用的身份验证方式,在 Express 应用中,我们可以使用 jsonwebtoken 库来实现。

安装 jsonwebtoken

npm install jsonwebtoken

假设我们有一个登录路由和一个需要身份验证的路由:

const express = require('express');
const jwt = require('jsonwebtoken');
const app = express();

const secretKey = 'your-secret-key';

// 登录路由
app.post('/login', (req, res) => {
  const { username, password } = req.body;
  // 验证用户名和密码
  if (username === 'admin' && password === 'password') {
    const token = jwt.sign({ username }, secretKey, { expiresIn: '1h' });
    res.send({ token });
  } else {
    res.status(401).send('Invalid credentials');
  }
});

// 需要身份验证的路由
const authenticateToken = (req, res, next) => {
  const token = req.headers['authorization'];
  if (!token) return res.status(401).send('Access denied. No token provided.');
  try {
    const decoded = jwt.verify(token.replace('Bearer ', ''), secretKey);
    req.user = decoded;
    next();
  } catch (error) {
    res.status(400).send('Invalid token');
  }
};

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

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

在上述代码中,登录成功后生成一个 JWT Token,在需要身份验证的路由中,通过 authenticateToken 中间件验证 Token 的有效性。

  1. 授权 授权是在身份验证的基础上,确定用户是否有权限访问特定资源。例如,我们可以根据用户角色来进行授权。

假设我们在用户的 JWT Token 中包含了角色信息:

// 登录路由
app.post('/login', (req, res) => {
  const { username, password } = req.body;
  // 验证用户名和密码
  if (username === 'admin' && password === 'password') {
    const token = jwt.sign({ username, role: 'admin' }, secretKey, { expiresIn: '1h' });
    res.send({ token });
  } else if (username === 'user' && password === 'password') {
    const token = jwt.sign({ username, role: 'user' }, secretKey, { expiresIn: '1h' });
    res.send({ token });
  } else {
    res.status(401).send('Invalid credentials');
  }
});

// 授权中间件
const authorize = (role) => {
  return (req, res, next) => {
    if (req.user.role === role) {
      next();
    } else {
      res.status(403).send('Forbidden');
    }
  };
};

// 只有管理员能访问的路由
app.get('/admin-only', authenticateToken, authorize('admin'), (req, res) => {
  res.send('This is an admin - only route');
});

// 用户能访问的路由
app.get('/user-only', authenticateToken, authorize('user'), (req, res) => {
  res.send('This is a user - only route');
});

在上述代码中,authorize 中间件根据用户的角色来决定是否允许访问特定路由。

安全的会话管理

  1. 会话存储 在 Express 应用中,我们可以使用 express - session 库来管理会话。对于会话存储,我们可以选择内存存储(适用于开发环境)或更安全的数据库存储,如 Redis。

安装 express - sessionconnect - redis(用于 Redis 存储):

npm install express - session connect - redis

使用 Redis 存储会话:

const express = require('express');
const session = require('express - session');
const RedisStore = require('connect - redis')(session);
const redis = require('redis');

const app = express();
const client = redis.createClient();

app.use(session({
  store: new RedisStore({ client }),
  secret: 'your - secret - key',
  resave: false,
  saveUninitialized: true
}));

app.get('/', (req, res) => {
  if (!req.session.views) {
    req.session.views = 1;
  } else {
    req.session.views++;
  }
  res.send(`You have visited this page ${req.session.views} times`);
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

在上述代码中,会话数据存储在 Redis 中,secret 用于加密会话 cookie。

  1. 会话 cookie 安全设置 为了增强会话 cookie 的安全性,我们可以设置一些属性,如 httponlysecurehttponly 防止 JavaScript 访问 cookie,secure 确保 cookie 只在 HTTPS 连接下传输。
app.use(session({
  store: new RedisStore({ client }),
  secret: 'your - secret - key',
  resave: false,
  saveUninitialized: true,
  cookie: {
    httponly: true,
    secure: true,
    maxAge: 1000 * 60 * 60 * 24 // 1天
  }
}));

上述代码中,httponly 设置为 truesecure 设置为 true,同时设置了 cookie 的最大有效期为 1 天。

防止暴力破解攻击

  1. 限制登录尝试次数 为了防止暴力破解用户登录密码,我们可以限制用户在一定时间内的登录尝试次数。我们可以使用 express - rate - limit 库来实现。

安装 express - rate - limit

npm install express - rate - limit
const express = require('express');
const rateLimit = require('express - rate - limit');
const app = express();

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15分钟
  max: 5, // 15分钟内最多5次尝试
  message: 'Too many login attempts. Please try again later.'
});

app.use(limiter);

app.post('/login', (req, res) => {
  const { username, password } = req.body;
  // 验证用户名和密码
  if (username === 'admin' && password === 'password') {
    res.send('Login successful');
  } else {
    res.status(401).send('Invalid credentials');
  }
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

在上述代码中,limiter 限制了用户在 15 分钟内最多进行 5 次登录尝试,超过次数返回错误信息。

  1. 使用验证码 验证码是一种常用的防止暴力破解和机器人攻击的方法。我们可以使用 captcha - generator 库来生成验证码图片。

安装 captcha - generator

npm install captcha - generator
const express = require('express');
const Captcha = require('captcha - generator');
const app = express();

app.get('/captcha', (req, res) => {
  const captcha = new Captcha(120, 40, 5);
  req.session.captcha = captcha.text;
  res.type('png').send(captcha.buffer);
});

app.post('/login', (req, res) => {
  const { username, password, captcha } = req.body;
  if (captcha.toLowerCase()!== req.session.captcha.toLowerCase()) {
    return res.status(400).send('Invalid captcha');
  }
  // 验证用户名和密码
  if (username === 'admin' && password === 'password') {
    res.send('Login successful');
  } else {
    res.status(401).send('Invalid credentials');
  }
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

在上述代码中,/captcha 路由生成验证码图片并将验证码文本存储在会话中,/login 路由验证用户输入的验证码是否正确。

安全的部署与环境配置

  1. HTTPS 配置 在生产环境中,使用 HTTPS 是至关重要的,它可以加密传输的数据,防止中间人攻击。我们可以使用 https 模块结合 SSL/TLS 证书来配置 Express 应用。

假设我们已经有了 SSL/TLS 证书(cert.pemkey.pem):

const express = require('express');
const https = require('https');
const fs = require('fs');
const app = express();

const options = {
  key: fs.readFileSync('key.pem'),
  cert: fs.readFileSync('cert.pem')
};

app.get('/', (req, res) => {
  res.send('This is an HTTPS - enabled server');
});

https.createServer(options, app).listen(443, () => {
  console.log('Server running on port 443 (HTTPS)');
});

在上述代码中,https.createServer 方法使用证书配置创建了一个 HTTPS 服务器。

  1. 环境变量与敏感信息管理 在开发和部署过程中,敏感信息,如数据库密码、API 密钥等,不应该硬编码在代码中。我们可以使用环境变量来管理这些信息。

.env 文件中设置环境变量:

DB_PASSWORD=your - database - password
SECRET_KEY=your - secret - key

使用 dotenv 库加载环境变量:

npm install dotenv
const express = require('express');
const dotenv = require('dotenv');
dotenv.config();

const app = express();

const dbPassword = process.env.DB_PASSWORD;
const secretKey = process.env.SECRET_KEY;

// 使用环境变量进行数据库连接等操作

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

在上述代码中,dotenv.config() 加载 .env 文件中的环境变量,process.env 可以获取这些变量的值。

  1. 生产环境优化 在生产环境中,我们还可以进行一些优化来提高安全性和性能。例如,启用 gzip 压缩来减少传输数据的大小,使用 compression 库:
npm install compression
const express = require('express');
const compression = require('compression');
const app = express();

app.use(compression());

app.get('/', (req, res) => {
  res.send('This is a compressed response');
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

上述代码中,compression 中间件启用了 gzip 压缩,对响应数据进行压缩后再发送给客户端。同时,在生产环境中,应该关闭开发模式下的调试信息输出,避免泄露敏感信息。

通过以上全面的安全性配置与防护措施,可以显著提高 Node.js Express 应用的安全性,降低被攻击的风险,为用户提供更可靠和安全的服务。