易杰云呼是个给做电话外呼的人用的软件,前端是一个单页应用,后端是 Node.js + Express + SQLite。 前段时间我把后端整个加固了一遍,这篇记一下都改了什么、为什么这么改。
先说背景:它跑在一台 2 核 2G 的服务器上,带宽 8Mbps,跑不起来太复杂的东西。 登录接口暴露在公网上,谁都能试密码。
第一件事:加图形验证码
光靠限流不够,得让”机器批量试密码”这件事本身变麻烦。所以加了个图形验证码。
做的时候有几个细节是特意处理的:
- 6 位,去掉 0、O、1、I、L 这几个容易看错的字符 —— 不然用户自己都输不进去。
- 每个字符随机旋转、随机颜色、随机大小,再叠 5 条干扰线和 30 个噪点。
- 5 分钟过期,一次性,用完即废;大小写不敏感。
直接输出 SVG 而不是图片,省掉了图片编码那一步,服务器压力小一点。
第二件事:四层限流
原来的限流只有一层,明显不够。现在从外到里是四层:
第 1 层 全局 所有 IP 加起来 1 分钟超过 1000 次 → 全局关掉登录 5 分钟第 2 层 单 IP 同一个 IP 1 分钟 5 次第 3 层 验证码 同一个 IP 1 分钟最多取 20 次验证码第 4 层 试错 同一个 IP 1 分钟内验证码错 5 次 → 禁 5 分钟登录的顺序也改了,现在是:
全局限流 → 单 IP 限流 → 验证码校验 → 密码校验。
验证码放在密码前面,等于在最外圈就先把机器挡掉,不让它消耗到密码比对那一步。
踩的坑:限流本身把服务器拖卡了
这个坑挺典型的。我一开始的限流是写文件的:每次登录请求都去
readFileSync 读一遍、再 writeFileSync 写回去。
结果就是登录明显变卡 —— 因为同步读写会把整个 Node 的事件循环堵住。 更糟的是,它本身还是个 DoS 漏洞:只要有人一直请求,磁盘就一直被读写。
后来改成了纯内存的 Map 计数,问题就没了。内存表也设了硬上限,超过一万条就删最旧的,防止无限增长。
开关防连点
这个是给用户用的,不是给攻击者用的。设置页里有几个开关,有人会一直点, 点一次发一次请求,服务器就被刷。
现在的规则是:同一个用户每点满 10 次,锁 10 秒,返回一句”手速太快,稍后再试 ⚡”。 纯内存实现,按 userId 记,不落盘。
一个历史遗留问题:设备列表里 50 个”在线”
这个困扰我挺久。用户反复登录之后,设备列表里会堆出一堆重复的在线设备, 因为每次登录都会发一个新 token,旧的没作废。
改法很直接:登录成功的时候,先把这台设备的旧 token 全部标成无效,只留最新一条。
另外管理员那边的设备列表也改了查询方式,用 JOIN 去取每个设备最新的一条记录, 这样历史脏数据也不会把界面撑爆。
还剩一个没改完的
联系人批量导入那里还有个卡顿没解决:现在是一行一行循环插入,而且插入还没结束就提前 把语句收尾了,号码多的时候既卡又可能丢数据。
要改成分批插入、等这一批真的写完了再往下走。这个我先记着,下次处理。
这一轮改完,后端从原来的一层限流变成了四层,验证码、防连点、token 治理都补上了。 整个过程最值得记的一条是:限流这种东西,自己也可能成为被攻击的点 —— 我第一版就是因为写文件,反而把服务器拖垮了。