Node.js系列(6)--安全实践指南
Node.js安全实践指南 🔒
引言
安全性是Node.js应用开发中的重中之重。本文将深入探讨Node.js安全实践,包括常见安全威胁、防护措施、安全框架等方面,帮助开发者构建安全可靠的Node.js应用。
安全实践概述
Node.js安全实践主要包括以下方面:
- 身份认证:用户认证、令牌管理、会话控制
- 访问控制:权限管理、角色控制、资源授权
- 数据安全:加密存储、传输安全、敏感信息保护
- 漏洞防护:注入攻击、XSS、CSRF防护
- 审计日志:安全日志、操作记录、异常监控
安全实践实现
安全管理器
// 安全管理器
class SecurityManager {
private static instance: SecurityManager;
private config: SecurityConfig;
private tokenManager: TokenManager;
private encryptionManager: EncryptionManager;
private auditLogger: AuditLogger;
private constructor() {
this.config = {
jwtSecret: process.env.JWT_SECRET || 'default-secret',
tokenExpiration: 3600,
passwordIterations: 10000,
saltLength: 32,
keyLength: 64
};
this.tokenManager = new TokenManager(this.config);
this.encryptionManager = new EncryptionManager(this.config);
this.auditLogger = new AuditLogger();
}
// 获取单例实例
static getInstance(): SecurityManager {
if (!SecurityManager.instance) {
SecurityManager.instance = new SecurityManager();
}
return SecurityManager.instance;
}
// 初始化安全管理器
init(config: SecurityConfig): void {
this.config = { ...this.config, ...config };
this.tokenManager.updateConfig(this.config);
this.encryptionManager.updateConfig(this.config);
}
// 用户认证
async authenticate(
username: string,
password: string
): Promise<AuthResult> {
try {
// 验证用户凭据
const user = await this.validateCredentials(username, password);
// 生成访问令牌
const token = this.tokenManager.generateToken({
userId: user.id,
username: user.username,
roles: user.roles
});
// 记录审计日志
this.auditLogger.log('authentication', {
username,
success: true,
timestamp: new Date()
});
return {
success: true,
token,
user
};
} catch (error) {
// 记录失败审计
this.auditLogger.log('authentication', {
username,
success: false,
error: error.message,
timestamp: new Date()
});
throw new SecurityError(
'Authentication failed',
'AUTH_FAILED',
error
);
}
}
// 验证用户凭据
private async validateCredentials(
username: string,
password: string
): Promise<User> {
// 从数据库获取用户
const user = await this.getUserByUsername(username);
if (!user) {
throw new Error('User not found');
}
// 验证密码
const isValid = await this.encryptionManager.verifyPassword(
password,
user.passwordHash,
user.passwordSalt
);
if (!isValid) {
throw new Error('Invalid password');
}
return user;
}
// 验证令牌
verifyToken(token: string): TokenPayload {
return this.tokenManager.verifyToken(token);
}
// 检查权限
checkPermission(
user: User,
resource: string,
action: string
): boolean {
// 检查用户角色
for (const role of user.roles) {
const permissions = this.getPermissionsForRole(role);
// 检查是否有所需权限
if (this.hasPermission(permissions, resource, action)) {
return true;
}
}
return false;
}
// 获取角色权限
private getPermissionsForRole(role: string): Permission[] {
// 从配置或数据库获取角色权限
return [];
}
// 检查权限
private hasPermission(
permissions: Permission[],
resource: string,
action: string
): boolean {
return permissions.some(permission =>
permission.resource === resource &&
permission.actions.includes(action)
);
}
// 加密敏感数据
encryptData(data: string): string {
return this.encryptionManager.encrypt(data);
}
// 解密数据
decryptData(encryptedData: string): string {
return this.encryptionManager.decrypt(encryptedData);
}
// 生成安全哈希
async hashPassword(
password: string
): Promise<{ hash: string; salt: string }> {
return this.encryptionManager.hashPassword(password);
}
// 验证请求
validateRequest(req: Request): void {
// 验证内容类型
this.validateContentType(req);
// 验证输入数据
this.validateInput(req);
// 检查CSRF令牌
this.validateCsrfToken(req);
}
// 验证内容类型
private validateContentType(req: Request): void {
const contentType = req.headers['content-type'];
if (!contentType || !this.isValidContentType(contentType)) {
throw new SecurityError(
'Invalid content type',
'INVALID_CONTENT_TYPE'
);
}
}
// 检查内容类型是否有效
private isValidContentType(contentType: string): boolean {
const validTypes = [
'application/json',
'application/x-www-form-urlencoded',
'multipart/form-data'
];
return validTypes.some(type => contentType.includes(type));
}
// 验证输入数据
private validateInput(req: Request): void {
// 检查XSS攻击
this.checkXSS(req.body);
// 检查SQL注入
this.checkSQLInjection(req.query);
// 检查命令注入
this.checkCommandInjection(req.params);
}
// 检查XSS攻击
private checkXSS(data: any): void {
const xssPattern = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi;
if (this.containsPattern(data, xssPattern)) {
throw new SecurityError(
'Potential XSS attack detected',
'XSS_DETECTED'
);
}
}
// 检查SQL注入
private checkSQLInjection(data: any): void {
const sqlPattern = /(\b(select|insert|update|delete|drop|union|exec)\b)/gi;
if (this.containsPattern(data, sqlPattern)) {
throw new SecurityError(
'Potential SQL injection detected',
'SQL_INJECTION_DETECTED'
);
}
}
// 检查命令注入
private checkCommandInjection(data: any): void {
const commandPattern = /(\b(eval|exec|system|spawn)\b)/gi;
if (this.containsPattern(data, commandPattern)) {
throw new SecurityError(
'Potential command injection detected',
'COMMAND_INJECTION_DETECTED'
);
}
}
// 检查数据中是否包含模式
private containsPattern(data: any, pattern: RegExp): boolean {
if (typeof data === 'string') {
return pattern.test(data);
}
if (typeof data === 'object') {
return Object.values(data).some(value =>
this.containsPattern(value, pattern)
);
}
return false;
}
// 验证CSRF令牌
private validateCsrfToken(req: Request): void {
const token = req.headers['x-csrf-token'];
const storedToken = req.session?.csrfToken;
if (!token || !storedToken || token !== storedToken) {
throw new SecurityError(
'Invalid CSRF token',
'INVALID_CSRF_TOKEN'
);
}
}
// 生成CSRF令牌
generateCsrfToken(): string {
return this.tokenManager.generateCsrfToken();
}
// 记录安全审计
logAudit(
action: string,
details: AuditDetails
): void {
this.auditLogger.log(action, details);
}
}
// 令牌管理器
class TokenManager {
private config: SecurityConfig;
constructor(config: SecurityConfig) {
this.config = config;
}
// 更新配置
updateConfig(config: SecurityConfig): void {
this.config = config;
}
// 生成JWT令牌
generateToken(payload: TokenPayload): string {
return jwt.sign(
payload,
this.config.jwtSecret,
{
expiresIn: this.config.tokenExpiration
}
);
}
// 验证JWT令牌
verifyToken(token: string): TokenPayload {
try {
return jwt.verify(
token,
this.config.jwtSecret
) as TokenPayload;
} catch (error) {
throw new SecurityError(
'Invalid token',
'INVALID_TOKEN',
error
);
}
}
// 生成CSRF令牌
generateCsrfToken(): string {
return crypto.randomBytes(32).toString('hex');
}
}
// 加密管理器
class EncryptionManager {
private config: SecurityConfig;
constructor(config: SecurityConfig) {
this.config = config;
}
// 更新配置
updateConfig(config: SecurityConfig): void {
this.config = config;
}
// 加密数据
encrypt(data: string): string {
const iv = crypto.randomBytes(16);
const key = crypto.scryptSync(
this.config.jwtSecret,
'salt',
32
);
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
let encrypted = cipher.update(data, 'utf8', 'hex');
encrypted += cipher.final('hex');
const authTag = cipher.getAuthTag();
return JSON.stringify({
iv: iv.toString('hex'),
encrypted,
authTag: authTag.toString('hex')
});
}
// 解密数据
decrypt(encryptedData: string): string {
const { iv, encrypted, authTag } = JSON.parse(encryptedData);
const key = crypto.scryptSync(
this.config.jwtSecret,
'salt',
32
);
const decipher = crypto.createDecipheriv(
'aes-256-gcm',
key,
Buffer.from(iv, 'hex')
);
decipher.setAuthTag(Buffer.from(authTag, 'hex'));
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
// 哈希密码
async hashPassword(
password: string
): Promise<{ hash: string; salt: string }> {
const salt = crypto.randomBytes(this.config.saltLength).toString('hex');
const hash = await new Promise<string>((resolve, reject) => {
crypto.pbkdf2(
password,
salt,
this.config.passwordIterations,
this.config.keyLength,
'sha512',
(err, derivedKey) => {
if (err) reject(err);
resolve(derivedKey.toString('hex'));
}
);
});
return { hash, salt };
}
// 验证密码
async verifyPassword(
password: string,
hash: string,
salt: string
): Promise<boolean> {
const verifyHash = await new Promise<string>((resolve, reject) => {
crypto.pbkdf2(
password,
salt,
this.config.passwordIterations,
this.config.keyLength,
'sha512',
(err, derivedKey) => {
if (err) reject(err);
resolve(derivedKey.toString('hex'));
}
);
});
return hash === verifyHash;
}
}
// 审计日志记录器
class AuditLogger {
private logStream: fs.WriteStream;
constructor() {
this.logStream = fs.createWriteStream('security-audit.log', {
flags: 'a'
});
}
// 记录审计日志
log(action: string, details: AuditDetails): void {
const logEntry = {
timestamp: new Date(),
action,
...details
};
this.logStream.write(JSON.stringify(logEntry) + '\n');
}
// 关闭日志流
close(): void {
this.logStream.end();
}
}
// 安全错误类
class SecurityError extends Error {
constructor(
message: string,
public code: string,
public originalError?: Error
) {
super(message);
this.name = 'SecurityError';
}
}
// 接口定义
interface SecurityConfig {
jwtSecret: string;
tokenExpiration: number;
passwordIterations: number;
saltLength: number;
keyLength: number;
}
interface User {
id: string;
username: string;
passwordHash: string;
passwordSalt: string;
roles: string[];
}
interface TokenPayload {
userId: string;
username: string;
roles: string[];
}
interface Permission {
resource: string;
actions: string[];
}
interface AuthResult {
success: boolean;
token?: string;
user?: User;
}
interface AuditDetails {
username?: string;
success: boolean;
error?: string;
timestamp: Date;
[key: string]: any;
}
interface Request {
headers: Record<string, string>;
body: any;
query: Record<string, string>;
params: Record<string, string>;
session?: {
csrfToken?: string;
};
}
// 使用示例
async function main() {
// 创建安全管理器
const securityManager = SecurityManager.getInstance();
// 初始化配置
securityManager.init({
jwtSecret: process.env.JWT_SECRET || 'your-secret-key',
tokenExpiration: 3600,
passwordIterations: 10000,
saltLength: 32,
keyLength: 64
});
// 用户认证
try {
const authResult = await securityManager.authenticate(
'username',
'password'
);
console.log('Authentication successful:', authResult);
} catch (error) {
console.error('Authentication failed:', error);
}
// 验证请求
const req: Request = {
headers: {
'content-type': 'application/json',
'x-csrf-token': 'token'
},
body: { data: 'example' },
query: {},
params: {},
session: {
csrfToken: 'token'
}
};
try {
securityManager.validateRequest(req);
console.log('Request validation successful');
} catch (error) {
console.error('Request validation failed:', error);
}
// 加密数据
const sensitiveData = 'sensitive information';
const encryptedData = securityManager.encryptData(sensitiveData);
console.log('Encrypted data:', encryptedData);
// 解密数据
const decryptedData = securityManager.decryptData(encryptedData);
console.log('Decrypted data:', decryptedData);
// 生成密码哈希
const password = 'user-password';
const { hash, salt } = await securityManager.hashPassword(password);
console.log('Password hash:', hash);
console.log('Password salt:', salt);
}
main().catch(console.error);
最佳实践与建议
-
身份认证
- 使用强密码策略
- 实现多因素认证
- 安全存储凭据
- 限制登录尝试
-
访问控制
- 最小权限原则
- 角色基础访问控制
- 资源级别权限
- 动态权限管理
-
数据安全
- 加密敏感数据
- 安全传输数据
- 定期数据备份
- 数据脱敏处理
-
安全防护
- 输入数据验证
- 防止注入攻击
- 配置安全头部
- 使用安全中间件
总结
Node.js安全实践需要考虑以下方面:
- 身份认证和访问控制
- 数据加密和安全传输
- 漏洞防护和安全配置
- 日志记录和安全审计
- 安全更新和维护
通过全面的安全实践,可以有效保护Node.js应用免受安全威胁。
学习资源
- Node.js安全指南
- Web安全最佳实践
- 加密算法指南
- 安全框架文档
- 漏洞防护教程
如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!👇
终身学习,共同成长。
咱们下一期见
💻