我在实现 React Hook 时犯了个错误,导致后端拒绝服务攻击。
在 Firebase 端点上使用 React 的 useEffect hook 看似简单,却在几分钟内意外地用完了我每天 5 万次的读取配额。
function Page() {
const [meetings, setMeetings] = useState([]);
useEffect(() => {
return firebase.firestore().collection('/meetings').onSnapshot(query => {
setMeeting( query.docs.map(m => m.data()) );
});
});
return (
<ul>
{meetings.map(m => <li>{m.title}</li>}
</ul>
)
}
由于该效果不依赖于任何状态改变,所以我省略了第二个参数。但我没有意识到的是,每次setMeeting调用该函数时,函数体都会再次执行,导致数据获取陷入循环。
是的。Firebase 的免费套餐每天提供 5 万次读取的配额,而我在几分钟的开发工作中就超过了这个限额。追踪问题来源也相当麻烦。当我意识到 Chrome 开发者工具中的“网络”选项卡正在向 Firebase 发送大量请求时,我不得不赶紧切换到性能分析选项卡并切换到“离线模式”。然后我才有时间查看其中一个请求的有效负载,并弄清楚它请求的是什么数据。
解决方法很简单,只需添加一个空方括号,表示这应该只运行一次,就像 componentDidMount 和 componentWillUnmount 一样(firebase 调用返回的是我们想要卸载的监听器)。
useEffect(() => {
// return firebase...
}, []) // this guy
不过,在修复问题之后,我停下来思考了一下其根本原因。
-
在效果钩子中,第二个参数很容易被忽略。
-
Firebase/Firestore 没有服务器端限速机制。因此,任何恶意用户或代码漏洞都可能导致免费用户服务中断,或者向付费用户收取高额费用。甚至连 Google 控制台中的配额管理工具都无法直观地显示哪个端点正在遭受攻击,以及攻击的具体时间。
-
当您在 Firebase 中的使用配额超出限制时,您甚至无法访问管理面板。
-
错误处理机制无法捕获这类问题。
祝您编程愉快!
参考
- 照片由 Andrew Gaines 拍摄,来自 Unsplash
- Firebase 是一种后端即服务 (BaaS),为实时数据存储解决方案提供慷慨的免费套餐。