您现在的位置是:网站首页 > 输入验证文章详情
输入验证
陈川
【
Node.js
】
6423人已围观
5192字
输入验证的重要性
输入验证是确保应用程序安全性的第一道防线。未经处理的用户输入可能导致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, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
};
// 在输出用户提供的内容时使用
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"]
}
}));
验证最佳实践
- 始终在服务端验证,即使前端已经验证
- 验证所有输入,包括URL参数、请求头和cookie
- 使用白名单而非黑名单策略
- 对输出进行编码
- 限制输入长度和类型
- 使用现有的验证库而非自己实现
- 记录失败的验证尝试以检测攻击行为