您现在的位置是:网站首页 > 输入验证文章详情

输入验证

输入验证的重要性

输入验证是确保应用程序安全性的第一道防线。未经处理的用户输入可能导致SQL注入、跨站脚本攻击(XSS)、命令注入等安全漏洞。Node.js作为服务端JavaScript运行时,处理用户输入时需要特别注意数据验证和清理。

基础验证方法

最简单的验证方式是检查输入是否存在以及是否符合预期格式:

function validateUsername(username) {
  if (!username) {
    throw new Error('用户名不能为空');
  }
  if (username.length < 3 || username.length > 20) {
    throw new Error('用户名长度必须在3-20个字符之间');
  }
  if (!/^[a-zA-Z0-9_]+$/.test(username)) {
    throw new Error('用户名只能包含字母、数字和下划线');
  }
  return true;
}

使用验证库

手动编写验证逻辑容易出错且难以维护。常用验证库如Joi、validator.js和express-validator可以简化这个过程:

const Joi = require('joi');

const userSchema = Joi.object({
  username: Joi.string().alphanum().min(3).max(30).required(),
  email: Joi.string().email().required(),
  password: Joi.string().pattern(new RegExp('^[a-zA-Z0-9]{8,30}$')),
  repeat_password: Joi.ref('password'),
  birth_year: Joi.number().integer().min(1900).max(2013),
});

防止SQL注入

使用参数化查询是防止SQL注入的关键:

// 错误方式 - 容易导致SQL注入
const query = `SELECT * FROM users WHERE username = '${username}'`;

// 正确方式 - 使用参数化查询
const query = 'SELECT * FROM users WHERE username = ?';
connection.query(query, [username], (error, results) => {
  // 处理结果
});

防范XSS攻击

输出编码是防范XSS攻击的重要手段:

const escapeHtml = (unsafe) => {
  return unsafe
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#039;");
};

// 在输出用户提供的内容时使用
const safeOutput = escapeHtml(userInput);

文件上传验证

处理文件上传时需要验证文件类型和大小:

const multer = require('multer');
const path = require('path');

const upload = multer({
  limits: {
    fileSize: 1024 * 1024 * 5 // 限制5MB
  },
  fileFilter: (req, file, cb) => {
    const filetypes = /jpeg|jpg|png|gif/;
    const extname = filetypes.test(path.extname(file.originalname).toLowerCase());
    const mimetype = filetypes.test(file.mimetype);
    
    if (mimetype && extname) {
      return cb(null, true);
    } else {
      cb('错误:只允许上传图片文件');
    }
  }
});

正则表达式验证

复杂验证场景可以使用正则表达式:

// 验证中国大陆手机号
function validateChinesePhone(phone) {
  const regex = /^(?:(?:\+|00)86)?1[3-9]\d{9}$/;
  return regex.test(phone);
}

// 验证强密码(至少8位,包含大小写字母和数字)
function validateStrongPassword(password) {
  const regex = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])[0-9a-zA-Z]{8,}$/;
  return regex.test(password);
}

自定义验证中间件

在Express中创建可重用的验证中间件:

const validateUserInput = (req, res, next) => {
  const { username, email, password } = req.body;
  
  // 验证用户名
  if (!username || username.length < 3) {
    return res.status(400).json({ error: '无效的用户名' });
  }
  
  // 验证邮箱
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  if (!email || !emailRegex.test(email)) {
    return res.status(400).json({ error: '无效的邮箱地址' });
  }
  
  // 验证密码强度
  if (!password || password.length < 8) {
    return res.status(400).json({ error: '密码必须至少8个字符' });
  }
  
  next();
};

// 在路由中使用
app.post('/register', validateUserInput, (req, res) => {
  // 处理注册逻辑
});

验证嵌套对象

处理复杂数据结构时需要递归验证:

function validateAddress(address) {
  if (!address) return false;
  
  const requiredFields = ['street', 'city', 'state', 'postalCode'];
  for (const field of requiredFields) {
    if (!address[field] || typeof address[field] !== 'string') {
      return false;
    }
  }
  
  // 验证邮政编码格式
  if (!/^\d{5}(-\d{4})?$/.test(address.postalCode)) {
    return false;
  }
  
  return true;
}

function validateUser(user) {
  if (!user.name || typeof user.name !== 'string') return false;
  if (!validateAddress(user.address)) return false;
  return true;
}

异步验证

有些验证需要异步操作,如检查用户名是否已存在:

async function isUsernameAvailable(username) {
  try {
    const user = await User.findOne({ username });
    return !user;
  } catch (error) {
    console.error('验证用户名时出错:', error);
    return false;
  }
}

// 在路由处理中使用
app.post('/check-username', async (req, res) => {
  const { username } = req.body;
  const available = await isUsernameAvailable(username);
  res.json({ available });
});

验证错误处理

提供清晰的错误信息有助于前端展示:

function validateRegistration(data) {
  const errors = {};
  
  if (!data.username) {
    errors.username = '用户名不能为空';
  } else if (data.username.length < 3) {
    errors.username = '用户名太短';
  }
  
  if (!data.email) {
    errors.email = '邮箱不能为空';
  } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) {
    errors.email = '无效的邮箱格式';
  }
  
  return {
    isValid: Object.keys(errors).length === 0,
    errors
  };
}

// 使用示例
const { isValid, errors } = validateRegistration(req.body);
if (!isValid) {
  return res.status(400).json({ errors });
}

内容安全策略

设置HTTP头增强安全性:

const helmet = require('helmet');

app.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"],
    scriptSrc: ["'self'", "'unsafe-inline'", "trusted.cdn.com"],
    styleSrc: ["'self'", "'unsafe-inline'"],
    imgSrc: ["'self'", "data:", "image.cdn.com"],
    fontSrc: ["'self'", "fonts.cdn.com"]
  }
}));

验证最佳实践

  1. 始终在服务端验证,即使前端已经验证
  2. 验证所有输入,包括URL参数、请求头和cookie
  3. 使用白名单而非黑名单策略
  4. 对输出进行编码
  5. 限制输入长度和类型
  6. 使用现有的验证库而非自己实现
  7. 记录失败的验证尝试以检测攻击行为

上一篇: 常见安全威胁

下一篇: 认证与授权

我的名片

网名:~川~

岗位:console.log 调试员

坐标:重庆市-九龙坡区

邮箱:cc@qdcc.cn

沙漏人生

站点信息

  • 建站时间:2013/03/16
  • 本站运行
  • 文章数量
  • 总访问量
微信公众号
每次关注
都是向财富自由迈进的一步