更新政府端和银行端
This commit is contained in:
239
bank-backend/middleware/security.js
Normal file
239
bank-backend/middleware/security.js
Normal file
@@ -0,0 +1,239 @@
|
||||
/**
|
||||
* 安全中间件
|
||||
* @file security.js
|
||||
* @description 处理安全相关的中间件
|
||||
*/
|
||||
const rateLimit = require('express-rate-limit');
|
||||
const helmet = require('helmet');
|
||||
const { body, validationResult } = require('express-validator');
|
||||
|
||||
/**
|
||||
* API请求频率限制
|
||||
*/
|
||||
const apiRateLimiter = rateLimit({
|
||||
windowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS) || 15 * 60 * 1000, // 15分钟
|
||||
max: parseInt(process.env.RATE_LIMIT_MAX_REQUESTS) || 100, // 限制每个IP 15分钟内最多100个请求
|
||||
message: {
|
||||
success: false,
|
||||
message: '请求过于频繁,请稍后再试'
|
||||
},
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
handler: (req, res) => {
|
||||
res.status(429).json({
|
||||
success: false,
|
||||
message: '请求过于频繁,请稍后再试'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 登录请求频率限制(更严格)
|
||||
*/
|
||||
const loginRateLimiter = rateLimit({
|
||||
windowMs: 15 * 60 * 1000, // 15分钟
|
||||
max: 5, // 限制每个IP 15分钟内最多5次登录尝试
|
||||
message: {
|
||||
success: false,
|
||||
message: '登录尝试次数过多,请15分钟后再试'
|
||||
},
|
||||
skipSuccessfulRequests: true,
|
||||
handler: (req, res) => {
|
||||
res.status(429).json({
|
||||
success: false,
|
||||
message: '登录尝试次数过多,请15分钟后再试'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 安全头部设置
|
||||
*/
|
||||
const securityHeaders = helmet({
|
||||
contentSecurityPolicy: {
|
||||
directives: {
|
||||
defaultSrc: ["'self'"],
|
||||
styleSrc: ["'self'", "'unsafe-inline'"],
|
||||
scriptSrc: ["'self'"],
|
||||
imgSrc: ["'self'", "data:", "https:"],
|
||||
connectSrc: ["'self'"],
|
||||
fontSrc: ["'self'"],
|
||||
objectSrc: ["'none'"],
|
||||
mediaSrc: ["'self'"],
|
||||
frameSrc: ["'none'"]
|
||||
}
|
||||
},
|
||||
crossOriginEmbedderPolicy: false
|
||||
});
|
||||
|
||||
/**
|
||||
* 输入数据清理
|
||||
*/
|
||||
const inputSanitizer = (req, res, next) => {
|
||||
// 清理请求体中的危险字符
|
||||
const sanitizeObject = (obj) => {
|
||||
if (typeof obj !== 'object' || obj === null) return obj;
|
||||
|
||||
for (const key in obj) {
|
||||
if (typeof obj[key] === 'string') {
|
||||
// 移除潜在的XSS攻击字符
|
||||
obj[key] = obj[key]
|
||||
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
|
||||
.replace(/javascript:/gi, '')
|
||||
.replace(/on\w+\s*=/gi, '');
|
||||
} else if (typeof obj[key] === 'object') {
|
||||
sanitizeObject(obj[key]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (req.body) sanitizeObject(req.body);
|
||||
if (req.query) sanitizeObject(req.query);
|
||||
if (req.params) sanitizeObject(req.params);
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
/**
|
||||
* 会话超时检查
|
||||
*/
|
||||
const sessionTimeoutCheck = (req, res, next) => {
|
||||
if (req.user && req.user.last_login) {
|
||||
const lastLogin = new Date(req.user.last_login);
|
||||
const now = new Date();
|
||||
const timeout = 24 * 60 * 60 * 1000; // 24小时
|
||||
|
||||
if (now - lastLogin > timeout) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '会话已超时,请重新登录'
|
||||
});
|
||||
}
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
/**
|
||||
* 验证错误处理中间件
|
||||
*/
|
||||
const handleValidationErrors = (req, res, next) => {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '输入数据验证失败',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
/**
|
||||
* 银行账户验证规则
|
||||
*/
|
||||
const validateAccountNumber = [
|
||||
body('account_number')
|
||||
.isLength({ min: 16, max: 20 })
|
||||
.withMessage('账户号码长度必须在16-20位之间')
|
||||
.matches(/^\d+$/)
|
||||
.withMessage('账户号码只能包含数字'),
|
||||
handleValidationErrors
|
||||
];
|
||||
|
||||
/**
|
||||
* 金额验证规则
|
||||
*/
|
||||
const validateAmount = [
|
||||
body('amount')
|
||||
.isFloat({ min: 0.01 })
|
||||
.withMessage('金额必须大于0')
|
||||
.custom((value) => {
|
||||
// 检查金额精度(最多2位小数)
|
||||
if (value.toString().split('.')[1] && value.toString().split('.')[1].length > 2) {
|
||||
throw new Error('金额最多支持2位小数');
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
handleValidationErrors
|
||||
];
|
||||
|
||||
/**
|
||||
* 身份证号验证规则
|
||||
*/
|
||||
const validateIdCard = [
|
||||
body('id_card')
|
||||
.matches(/^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/)
|
||||
.withMessage('身份证号码格式不正确'),
|
||||
handleValidationErrors
|
||||
];
|
||||
|
||||
/**
|
||||
* 手机号验证规则
|
||||
*/
|
||||
const validatePhone = [
|
||||
body('phone')
|
||||
.matches(/^1[3-9]\d{9}$/)
|
||||
.withMessage('手机号码格式不正确'),
|
||||
handleValidationErrors
|
||||
];
|
||||
|
||||
/**
|
||||
* 密码验证规则
|
||||
*/
|
||||
const validatePassword = [
|
||||
body('password')
|
||||
.isLength({ min: 6, max: 20 })
|
||||
.withMessage('密码长度必须在6-20位之间')
|
||||
.matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/)
|
||||
.withMessage('密码必须包含大小写字母和数字'),
|
||||
handleValidationErrors
|
||||
];
|
||||
|
||||
/**
|
||||
* 防止SQL注入的查询参数验证
|
||||
*/
|
||||
const validateQueryParams = (req, res, next) => {
|
||||
const dangerousPatterns = [
|
||||
/(\b(SELECT|INSERT|UPDATE|DELETE|DROP|CREATE|ALTER|EXEC|UNION|SCRIPT)\b)/i,
|
||||
/(--|\/\*|\*\/|xp_|sp_)/i,
|
||||
/(\bOR\b|\bAND\b).*(\bOR\b|\bAND\b)/i
|
||||
];
|
||||
|
||||
const checkObject = (obj) => {
|
||||
for (const key in obj) {
|
||||
if (typeof obj[key] === 'string') {
|
||||
for (const pattern of dangerousPatterns) {
|
||||
if (pattern.test(obj[key])) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '检测到潜在的安全威胁'
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (typeof obj[key] === 'object' && obj[key] !== null) {
|
||||
checkObject(obj[key]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
checkObject(req.query);
|
||||
checkObject(req.body);
|
||||
checkObject(req.params);
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
apiRateLimiter,
|
||||
loginRateLimiter,
|
||||
securityHeaders,
|
||||
inputSanitizer,
|
||||
sessionTimeoutCheck,
|
||||
handleValidationErrors,
|
||||
validateAccountNumber,
|
||||
validateAmount,
|
||||
validateIdCard,
|
||||
validatePhone,
|
||||
validatePassword,
|
||||
validateQueryParams
|
||||
};
|
||||
Reference in New Issue
Block a user