大家好!各位Web开发者。💻
今天,我想和大家聊一聊在使用Node.js和Express.js构建Web应用程序时一个非常非常重要但又容易被忽视的安全设置。那就是如何防止CSRF(Cross-Site Request Forgery,跨站请求伪造)攻击。
您是否在中间件配置部分见过以下代码?
// 启用Express CSRF保护
app.use(csrf());
// 使CSRF令牌在模板中可用
app.use((req, res, next) => {
res.locals.csrftoken = req.csrfToken();
next();
});
我们将深入浅出地分析这两段代码如何保护我们的服务器,以及它们背后的原理!

🤔 等等,CSRF攻击到底是什么?
在查看代码之前,我们先简单了解一下我们想要阻止的敌人——CSRF是什么。
CSRF是一种让用户在不知情的情况下,执行攻击者意图的操作的攻击。
举个例子吧?
- 用户 👤 已登录my-bank.com。浏览器中保留着my-bank.com的认证信息(Cookie)。
- 此时,用户点击了恶意邮件或帖子中包含的链接 evil-site.com。😈
- evil-site.com 页面中隐藏着看不见的代码。例如,可能隐藏着一个自动发送 my-bank.com/transfer?to=hacker&amount=1000 这样请求的图片标签或表单。
- 用户的浏览器在发送此请求时,会自动附带my-bank.com的认证Cookie。🍪
- 银行服务器 🏦 呢?它收到包含正常认证Cookie的请求,就会误以为“啊,是正常用户在请求转账啊!”,然后将钱转到攻击者的账户。
太可怕了,不是吗?用户只是点击了一个链接,却在不知不觉中,重要的信息被更改或遭受经济损失,这是一种非常危险的攻击。
🔐 第一步:激活CSRF防御盾!app.use(csrf());
现在我们来看看第一段代码。
// 启用Express CSRF保护
app.use(csrf());
这一行代码就像是为我们的Express服务器安装了一个强大的防御盾。添加这段代码后,服务器将开始执行以下操作:
- 发放秘密钥匙(CSRF令牌)🔑:服务器为每个用户会话生成一个独一无二且不可预测的字符串,即“CSRF令牌”。这个令牌就像一次性密码。
- 监控和验证请求 🧐:从现在开始,服务器会检查所有改变状态的请求(POST、PUT、DELETE等),确保其中正确包含了这个秘密钥匙(CSRF令牌)。
- 如果请求中没有令牌,或者与服务器发放的令牌不匹配怎么办? ➡️ 拦截! ❌ 服务器会将该请求视为危险请求,并抛出错误,拒绝该请求。
现在,即使攻击者 😈 从evil-site.com偷偷发送请求,由于他们不知道这个秘密钥匙(CSRF令牌),服务器也会拦截该请求。这样就可以阻止大部分CSRF攻击!
💌 第二步:安全地将秘密钥匙传递给前端
好了,现在服务器已经准备好创建和验证秘密钥匙了。但是还有一个问题。
“用户正常发送的请求中,如何包含这个秘密钥匙呢?” 🤔
仅仅服务器知道秘密钥匙是不行的。用户端(前端)也需要接收这个钥匙,并在发送请求时一并发送。这正是第二段代码的作用。
// 使CSRF令牌在模板中可用
app.use((req, res, next) => {
res.locals.csrftoken = req.csrfToken();
next();
});
这段代码会为每个请求依次执行以下操作:
- 调用req.csrfToken():调用csrf()中间件在请求(req)对象上创建的csrfToken()函数。此函数返回当前会话的秘密钥匙(CSRF令牌)。
- 存储到res.locals:res.locals是一个特殊的“口袋”,用于存储仅在处理当前请求期间有效的数据。我们将刚刚获取的秘密钥匙以csrftoken的名称存储在这里。
- 可在模板中使用:存储在res.locals中的值可以在EJS、Pug、Nunjucks等模板引擎中直接像变量一样使用。这意味着在渲染HTML时,可以轻松地嵌入这个秘密钥匙的值!
✨ 最后一步:在前端使用秘密钥匙
现在所有准备工作都已完成。在前端模板(例如:form.ejs文件)中,可以像下面这样使用存储在res.locals中的令牌:
<form action="/update-profile" method="POST">
<input type="hidden" name="_csrf" value="<%= csrftoken %>">
<div>
<label for="username">이름:</label>
<input type="text" id="username" name="username">
</div>
<button type="submit">프로필 업데이트</button>
</form>
这样,当用户点击“更新个人资料”按钮提交表单时,我们的秘密钥匙(CSRF令牌)就会包含在不可见的 _csrf 字段中并发送到服务器。服务器看到这个令牌后,会判断“啊,这是我发放了秘密钥匙的正常请求!”,然后安全地处理该请求。✅
总结
今天我们探讨的两行代码构建了以下完美的CSRF防御体系:
- app.use(csrf());: 生成CSRF令牌,并监控和验证所有传入请求,激活防御盾。
- app.use((req, res, next) => { … });: 将防御所需的CSRF令牌安全地传递给前端模板。
- 前端: 将接收到的令牌包含在表单的隐藏字段中,作为“我是正常用户”的凭证一并发送给服务器。
Web安全再怎么强调也不为过。特别是对于处理用户重要信息的服务,CSRF防御不是可选项,而是必需项。希望这段代码能帮助您使您的宝贵服务更加安全。
祝您编程愉快!🚀
标签: Nodejs, Express, CSRF, Security, WebDev, csurf, Web安全, Node.js
发表回复