安全不是可选项

2025 年 OWASP Top 10 里,失效的访问控制、注入攻击、加密失败仍然是最高频的安全威胁。对于中小网站来说,攻击者不会因为你规模小就放过你——自动扫描脚本不在乎目标是谁。

好消息是:Node.js 内置模块足以构建坚固的安全防线。

第一道防线:密码哈希

永远不要存储明文密码。SHA-256 也不够——因为 GPU 可以每秒计算数十亿次 SHA-256,暴力破解成本太低。

正确做法是用 scrypt——专门设计来对抗暴力破解的密钥派生函数:

const crypto = require('crypto');

function hashPassword(password) {
  const salt = crypto.randomBytes(16).toString('hex');
  const hash = crypto.scryptSync(password, salt, 64).toString('hex');
  return `${salt}:${hash}`;
}

function verifyPassword(password, stored) {
  const [salt, hash] = stored.split(':');
  return hash === crypto.scryptSync(password, salt, 64).toString('hex');
}

scrypt 的设计目标是「内存密集」——每次哈希需要分配大量内存,这让 GPU 并行暴力破解变得极度昂贵。

第二道防线:XSS 防护

任何来自用户输入的数据,写入数据库之前都要转义:

function esc(str) {
  return String(str)
    .replace(/&/g, '&')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;');
}

注意:不是在输出时转义,而是在存储前转义。这样即使前端忘了转义,数据库里存的就是安全的。

第三道防线:速率限制

没有速率限制的 API 是免费算力——攻击者可以无限次尝试密码,或把服务器当代理。

const rateLimit = new Map();
const RATE_LIMIT = 60;
const RATE_WINDOW = 60000; // 1 分钟

function checkRate(ip) {
  const now = Date.now();
  const r = rateLimit.get(ip);
  if (!r || now - r.reset > RATE_WINDOW) {
    rateLimit.set(ip, { count: 1, reset: now });
    return true;
  }
  r.count++;
  return r.count <= RATE_LIMIT;
}

每 IP 每分钟 60 请求——正常用户不可能达到,但脚本一秒就能冲破。

第四道防线:原子写入

JSON 文件作数据库,最大的风险是写入一半进程崩溃导致数据损坏。解决方案来自操作系统:

function writeJSON(filename, data) {
  const p = path.join(DATA_DIR, filename);
  fs.writeFileSync(p + '.tmp', JSON.stringify(data));
  fs.renameSync(p + '.tmp', p);
}

rename原子系统调用。要么完整成功,要么完全不变。不存在中间状态。

第五道防线:目录遍历防护

攻击者可能会尝试通过 URL 访问服务器上的任意文件:

GET /../../../etc/passwd

防护很简单——检查解析后的路径是否在项目根目录内:

const ROOT_SAFE = ROOT + path.sep;
if (!fullPath.startsWith(ROOT_SAFE)) {
  return send(res, 403, { error: 'Forbidden' });
}

安全响应头

最后补上几个 HTTP 头,花 3 行代码挡住大量常见攻击:

res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');

小结

零依赖不等于零安全。Node.js 的 cryptofszlib 模块 + 正确的架构设计 = 完全可以达到生产级安全性。关键不在于用了多少库,在于是否理解每条防线背后的原理。