Data validation and input filtering
The Necessity of Data Validation
Data validation is the first line of defense for web application security. Unvalidated user input can lead to security vulnerabilities such as SQL injection and cross-site scripting (XSS) attacks. Express, as a popular Node.js framework, directly handles user input data when processing HTTP requests, making it essential to strictly validate all incoming data.
// Dangerous example: Directly using unvalidated request body
app.post('/login', (req, res) => {
const username = req.body.username;
const password = req.body.password;
// Using these values directly for database queries
});
Basic Validation Techniques
Type Checking
JavaScript's dynamic typing requires explicit data type checks:
function validateNumber(input) {
if (typeof input !== 'number' || isNaN(input)) {
throw new Error('Must input a valid number');
}
return Number(input);
}
Regular Expression Validation
Suitable for complex format validation, such as emails and URLs:
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
function validateEmail(email) {
if (!emailRegex.test(email)) {
throw new Error('Invalid email format');
}
return email.trim();
}
Express Middleware Validation
Using express-validator
const { body, validationResult } = require('express-validator');
app.post('/user',
body('username').isLength({ min: 5 }).trim().escape(),
body('email').isEmail().normalizeEmail(),
body('age').isInt({ min: 18 }),
(req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
// Process valid data
}
);
Input Filtering Techniques
HTML Tag Filtering
Basic measures to prevent XSS attacks:
const sanitizeHtml = require('sanitize-html');
const clean = sanitizeHtml(dirtyHtml, {
allowedTags: ['b', 'i', 'em', 'strong', 'a'],
allowedAttributes: {
'a': ['href']
},
allowedIframeHostnames: ['www.youtube.com']
});
SQL Injection Protection
Use parameterized queries instead of string concatenation:
// Wrong way
const query = `SELECT * FROM users WHERE username = '${username}'`;
// Correct way
const query = 'SELECT * FROM users WHERE username = ?';
connection.query(query, [username], (error, results) => {});
File Upload Validation
File type and content validation are equally important:
const multer = require('multer');
const upload = multer({
limits: { fileSize: 1024 * 1024 * 5 }, // 5MB limit
fileFilter: (req, file, cb) => {
if (!file.originalname.match(/\.(jpg|jpeg|png|gif)$/)) {
return cb(new Error('Only image files are allowed'));
}
cb(null, true);
}
});
app.post('/upload', upload.single('avatar'), (req, res) => {
// Process uploaded file
});
Custom Validators
Create reusable validation logic:
const validatePassword = (value) => {
if (value.length < 8) {
throw new Error('Password must be at least 8 characters');
}
if (!/[A-Z]/.test(value)) {
throw new Error('Password must contain an uppercase letter');
}
if (!/[0-9]/.test(value)) {
throw new Error('Password must contain a number');
}
return value;
};
app.post('/change-password',
body('newPassword').custom(validatePassword),
// ...other route logic
);
Deep Object Validation
Handling nested data structures:
const { body } = require('express-validator');
app.post('/order',
body('customer.name').notEmpty().trim(),
body('customer.email').isEmail(),
body('items.*.productId').isMongoId(),
body('items.*.quantity').isInt({ min: 1 }),
// ...validation error handling
);
Validation Error Handling
Unified validation error handling:
app.use((err, req, res, next) => {
if (err instanceof ValidationError) {
return res.status(422).json({
errors: err.array().map(e => ({
param: e.param,
message: e.msg,
location: e.location
}))
});
}
next(err);
});
Performance Considerations
Validation can impact performance; be mindful of:
// Avoid recompiling regular expressions in routes
const patterns = {
username: /^[a-z0-9_-]{3,16}$/,
password: /^(?=.*[A-Z])(?=.*[0-9]).{8,}$/
};
// Use early returns to avoid unnecessary validation
function validateUser(user) {
if (!user) return false;
if (!patterns.username.test(user.username)) return false;
// Other validations...
return true;
}
Client-Side vs. Server-Side Validation
Both must remain consistent:
// Client-side validation
function validateForm() {
const username = document.getElementById('username').value;
if (!/^[a-z0-9_-]{3,16}$/.test(username)) {
showError('Username must be 3-16 alphanumeric characters');
return false;
}
return true;
}
// Server-side must repeat these validations
Security Headers Setup
Additional defensive measures:
const helmet = require('helmet');
app.use(helmet());
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"],
styleSrc: ["'self'", "'unsafe-inline'"]
}
}));
Logging
Log suspicious inputs:
app.use((req, res, next) => {
if (req.body && containsMaliciousInput(req.body)) {
logSuspiciousActivity(req);
}
next();
});
function containsMaliciousInput(obj) {
return JSON.stringify(obj).match(/(<script>|SELECT \* FROM|DROP TABLE)/i);
}
Testing Validation Logic
Automated testing of validation rules:
const request = require('supertest');
const app = require('../app');
describe('User Registration Validation', () => {
it('Should reject invalid email', async () => {
const res = await request(app)
.post('/register')
.send({ email: 'invalid', password: 'Valid123!' });
expect(res.statusCode).toEqual(400);
});
});
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:Promise.prototype.finally()
下一篇:认证授权方案实现