别让短信验证码拖垮你:vibe 项目的登录与防刷实战
短信验证码是最快上线的登录方式,也是最容易被坑的。这篇指南讲透三个坑(成本、短信轰炸、模板合规),给出一套可直接上线的发送/校验流程(含限流),纵深防刷、成本控制,以及什么时候该换邮箱验证码或社交登录。附上线 checklist 和可直接抄的校验逻辑。

几乎每个 vibe 项目都会走到这一步:用户说"用手机号登录吧,方便"。你让 AI agent 接个阿里云/腾讯云短信,半小时调通,测试时收到"您的验证码是 482913",成就感拉满。然后上线第一周,账单来了:短信费几百块——而你的注册用户只有 40 个。
短信验证码是登录方案里的"甜蜜陷阱":接入门槛最低,翻车姿势最多。这篇指南把三个真实的坑(成本、被刷、合规)一次讲透,并给出一套可以直接上线的发送/校验流程、防刷纵深和成本控制手段。目标只有一个:让短信验证码从"定时炸弹"变成"可控成本项"。
坑一:成本——每条几分钱,架不住有人帮你"消费"
先算一笔账。国内短信验证码单价大约在 3–5 分/条,听起来便宜。但注意两个细节:第一,验证码场景的用户行为是"收不到就点重发",正常用户一次登录可能触发 2–3 条;第二,一旦接口被刷,攻击者一晚上能给你刷掉几千条,那就是几百块真金白银。对月入几千块的 side project 来说,一次被刷就可能吃掉整月利润。
更隐蔽的成本是"到达率"。验证码短信有一定比例发不出去(空号、携号转网、运营商拦截),但这部分很多套餐是照样计费的。所以选服务商时别只看单价,问清楚三件事:失败是否计费、是否有发送量突增告警、有没有测试号/测试模板不计费。阿里云、腾讯云都提供测试白名单,开发阶段务必先用白名单,别拿真实手机号联调——联调一天烧掉几十块的,大有人在。
坑二:短信轰炸——你的接口是别人的武器
这是最危险的一个坑。攻击者不需要攻破你的系统,他只需要用你的"发送验证码"接口,去轰炸别人的手机号:写个脚本循环请求,你的服务器就成了免费的短信轰炸机。受害者报警,被封的是你的短信签名;运营商判定你发送骚扰短信,轻则限流,重则直接停掉你的短信服务。
防轰炸的核心就一句话:发送验证码之前,先证明"你是人,且你真的是这个手机号的主人"。落到工程上,是四层纵深:
- 第一层:频率限制(必须有)。同一手机号 60 秒内只能发 1 次,每天最多 5–10 次;同一 IP 每小时最多请求 N 次(N 按你的业务量定,初期 20–30 是个合理起点)。注意:IP 限流要宽松一点,公司/学校出口 IP 是共享的,太严会误伤正常用户。
- 第二层:人机验证前置。在"发送验证码"按钮之前,加一道图形验证码或行为验证(滑块、无感验证)。这是性价比最高的防刷手段:脚本最怕的就是多这一步。用户体验确实会打折扣,但"多一次滑动"和"半夜被刷几千条"之间,选哪个很清楚。
- 第三层:发送后冷却与尝试次数。验证码 5–10 分钟有效,同一手机号新码发出后旧码立即作废;校验时最多允许 5 次输错,超限锁定 30 分钟。这能防住"撞库式"爆破 6 位数字码。
- 第四层:异常监控告警。发送量按小时统计,突增(比如平时每小时几十条、突然上千)立刻告警并自动触发更严的限流。这是最后的保险丝,救过很多人的命。
坑三:合规——签名和模板,错一个字都发不出去
国内短信和海外完全是两个世界。在国内,短信签名必须提前报备(一般是公司名、产品名或商标),模板必须逐条审核,格式通常是"您的验证码是{code},5 分钟内有效"。审核一般要 1–2 个工作日,所以短信能力要提前申请,不要等到上线前一天。
几个实战经验:签名能短则短,里的字数也算短信长度,超 70 字按长短信分两条计费;模板里的变量用 {code} 这类明确标记,不要在模板里加营销话术——验证码模板混入营销内容是被重点打击的;如果你的产品面向海外用户,记得海外短信走国际通道,单价是国内的 10 倍以上,这时候要认真考虑下面的替代方案。
一套可以直接上线的流程
把上面的坑都填上,标准的发送/校验流程长这样(伪代码,Redis 存状态):
// 发送验证码
async function sendSmsCode(phone, ip, captchaToken) {
// 1. 先验人机
if (!await verifyCaptcha(captchaToken)) throw new Error("人机验证失败");
// 2. 频率限制:手机号 60s/次、日 10 次;IP 小时 30 次
await checkRateLimit(`sms:phone:${phone}`, 60, 1); // 60秒窗口1次
await checkRateLimit(`sms:phone:day:${phone}`, 86400, 10);
await checkRateLimit(`sms:ip:hour:${ip}`, 3600, 30);
// 3. 生成 6 位随机码,crypto 安全随机,不要用 Math.random()
const code = cryptoRandom6Digits();
// 4. 存 Redis:只存 code 的 hash,不存明文;5 分钟过期
await redis.setex(`otp:${phone}`, 300, sha256(code));
await redis.setex(`otp:attempts:${phone}`, 300, 0);
// 5. 调用服务商发送;失败要打日志、计入监控
await smsProvider.send(phone, templateId, { code});
}
// 校验验证码
async function verifySmsCode(phone, inputCode) {
const attempts = await redis.incr(`otp:attempts:${phone}`);
if (attempts > 5) throw new Error("尝试次数过多,请 30 分钟后再试");
const hash = await redis.get(`otp:${phone}`);
if (!hash || hash!== sha256(inputCode)) throw new Error("验证码错误或已过期");
// 校验通过立即删除,一码一用
await redis.del(`otp:${phone}`, `otp:attempts:${phone}`);
return true;
}
几个容易被 AI agent 写错的细节,review 时重点看:验证码必须用密码学安全随机数(Math.random 可预测);Redis 里存 hash 不存明文(日志里也别打明文);校验通过后立即删除,做到一码一用;新码发出旧码作废(上面的 setex 天然覆盖了这一点);错误信息不要区分"手机号不存在"和"验证码错误"——统一返回"验证码错误",避免攻击者枚举注册用户。
成本控制:四招把短信费压到最低
- 测试环境用固定测试码:开发/测试环境拦截真实发送,直接返回固定验证码(如 123456),并在管理后台可配。这是省钱最多的一招。
- 登录态复用,别动不动就发码:access token + refresh token 做好,30 天免登录。很多项目的短信费大头是"用户每次打开都要收一次验证码",纯属设计问题。
- 先上邮箱验证码,再补短信:邮箱验证码几乎零成本。对非强实名场景,默认邮箱登录、短信作为可选项,能省掉 80% 以上的短信量。
- 监控 + 预算告警:给短信服务商账号设月度预算告警,发送量日报推送到你的手机。被刷的时候,早一小时发现就是少几百块。
什么时候该放弃短信:替代方案选型
说到底,短信验证码不是必选项。按场景选:
- 国内 C 端、需要实名或高转化:短信验证码仍是转化率最高的方案,值得投入防刷成本。
- 海外用户为主:国际短信贵且到达率不稳定,优先邮箱验证码 + 社交登录(Google/Apple)。
- 开发者工具/极客产品:GitHub 登录一把梭,用户甚至觉得输手机号很"重"。
- 微信生态内:微信一键登录/手机号快捷登录,用户体验和成本都优于自建短信。
我的观点是:独立开发者的默认选项应该是"邮箱验证码 + 一个社交登录",而不是短信。短信只在你验证了"用户真的需要它"之后再加。很多项目上线第一天就接短信,纯粹是因为"AI agent 说这是标准做法"——标准做法是给有风控团队的大厂准备的,你没有。
一个典型的翻车时间线(看看你中了几条)
这类事故的剧本高度雷同,值得对照自查:Day 1,agent 接好短信接口,没有任何限流,直接上线;Day 3,有人在论坛分享"这个站的验证码接口没做限制",脚本小子写了个 20 行的循环;Day 4 凌晨,你的短信服务商触发风控,签名被暂停——正在注册的真实用户收不到验证码,开始骂;Day 5,你收到账单:3 天发了 2 万条,其中 90% 是攻击者刷的。修复只用了半天(加上面的四层纵深),但签名申诉解封花了一周,损失的用户信任补不回来。
这个剧本里最贵的部分从来不是短信费,而是签名被封后真实用户流失的那一周。所以防刷不是"等被刷了再加",而是和短信接口同一天上线的标配。记住顺序:限流和人机验证写在第一版代码里,而不是 TODO 里。
上线 checklist:发布前逐项打勾
- □ 短信签名、模板已过审,生产环境模板 ID 已配置
- □ 手机号 60 秒/次、日 10 次限流已生效(含 IP 维度)
- □ 发送前有人机验证(图形/滑块/无感其一)
- □ 验证码 crypto 随机、Redis 存 hash、5–10 分钟过期、一码一用
- □ 校验失败 5 次锁定,错误信息不区分"用户不存在"
- □ 测试环境拦截真实发送,走固定测试码
- □ 发送量突增告警已配置(阈值按日常量的 5–10 倍设)
- □ 服务商账号已设月度预算告警
- □ 日志中不记录验证码明文
最后再强调一遍这篇指南的核心判断:短信验证码的风险从来不在"接不接得上",而在"上线之后"。AI agent 帮你半小时调通接口很容易,但防刷、限流、告警、合规这些"上线之后的事",agent 不会主动替你想到。发布前花一小时把上面的 checklist 过一遍,省下的可能是你项目第一个月的全部利润。
评论 (0)
相关文章

SaaS 开放 API 就要给用户发 Key:明文存库、一 Key 走天下、无法吊销、用量算不清,是 solo 团队最常见的四个坑。这篇指南给你一套可直接抄走的体系——Key 格式设计(前缀+熵+展示位)、只存哈希的存储方案、scope 权限模型、轮换/吊销 SOP、按 Key 限流与用量归因,附 TS 代码模板、Prisma 表结构、用量归因 SQL 与一周落地清单。

只要 vibe 项目有用户能写字的地方,你就得做审核——但审核不是删帖,是分级处置。本指南先画三类风险地图(传统 UGC、用户可发布内容、AI 生成输出),再搭三层漏斗:机器做 90%(关键词+AI 审核 API),用户举报当传感器(30 秒表单+分级 SLA),你每周只花 30 分钟处理 1% 的难案。附一页纸社区准则模板、防 prompt 注入三道防线、灾难熔断三档开关,一人团队直接照抄。

最便宜的 GPU 是用户的 GPU。这篇指南讲一人团队如何把合适的推理任务推到浏览器与端侧:WebGPU、transformers.js、WebLLM、llama.cpp、Ollama、Apple MLX 五条路线对照;1B–8B 量化模型的任务对照表;端侧先行、云端兜底的降级链与可直接套用的回退代码;把"数据不出设备"包装成营销卖点;首次加载体积、内存、发热的三条体验红线;以及两周跑完一个端侧功能试点的落地 SOP。云端只做兜底。