凌晨一点半,手机在床头柜上跳了一下。我以为是群里谁半夜发红包,摸起来一看,是云监控的告警:站点 5xx 错误率 12%。
爬起来开电脑,日志翻出来一看,注册接口一秒钟被打六次,清一色随机字符串邮箱,注册完就领新人优惠券,领完再也不登录。第二天早上看统计,一夜之间多了 3000 多个死账号,优惠券被薅走一大把。
我这小破站平时一天访问量也就三位数,头一回体会到"被惦记"的感觉。
我试过的三件蠢事
第一件:手写图形验证码。找了段 PHP 生成四位数字的代码贴上,心里还挺美。两周后被人拿开源 OCR 破了,识别率比我肉眼还高。
第二件:按 IP 封。封了一天,对方换代理池接着打,我这边封 IP 的列表倒是有两千多条了,跟集邮似的。
第三件:加个"请输入 1+1 等于几"。这个更离谱,脚本连算都懒得算,直接遍历答案——毕竟答案空间只有 18 种。
三件事做完我算是想明白了:自己写验证码,本质上是跟一个比你勤奋一百倍、还不用睡觉的脚本比赛更新速度。这个赛道你赢不了。
人机验证:把这件事交给专业的人
后来我把念头转到了阿里云的验证码 Captcha。它是新一代的验证码服务,逻辑不是"让用户做题",而是"在用户没感觉的情况下判断对面是不是人"——采集设备信息、交互行为、环境特征,交给风控模型去判断,风险高的才弹验证。
它一共提供几种验证形态,选型比想象中重要:
| 验证形态 | 交互方式 | 适合场景 | 我的建议 |
|---|---|---|---|
| 无痕验证 | 用户基本无感知 | 登录、注册、查询 | 首选,转化损失最小 |
| 滑动验证 | 拖一下滑块 | 投票、发帖、免费下载 | 通用稳妥 |
| 拼图验证 | 把碎片拖到缺口 | 高价值操作 | 强度更高 |
| 图像复原 | 滑动补全图片(独家) | 强风控场景 | 拦得最狠 |
| 一点即过 | 点一下即可 | 轻量交互 | 轻量动作首选 |
我最后选的是无痕验证放在注册接口。理由很实在:正常用户压根看不见验证码,被拦的只有脚本。
官方说三步,实际操作是四步
官方文档写的接入流程,客户端确实只要三步。但完整跑通要把服务端一起算上。
第 0 步,开通服务。 控制台点开通,按量版可以 0 元开通,开通后在概览页拿到身份标(prefix),客户端要用。这里顺手做一件正经事:别用主账号 AccessKey,建个 RAM 子账号,给它 AliyunYundunAFSFullAccess 就行。
第 1 步,建验证场景。 场景管理里新建场景,填场景名称、接入方式(Web/H5 还是 App)、验证码形态,拿到场景 ID(SceneId)。App 接入要选 Webview+H5,小程序走原生插件那条线。
第 2 步,客户端接入。 代码不长,我把骨架放在这儿,参数按注释替换:
<!-- 1. 全局变量,必须在引入 JS 之前 -->
<script>
window.AliyunCaptchaConfig = {
region: "cn", // 中国内地 cn;新加坡 sgp
prefix: "你的身份标"
};
</script>
<!-- 2. 动态引入验证码 JS -->
<script src="https://o.alicdn.com/captcha-frontend/aliyunCaptcha/AliyunCaptcha.js"></script>
<!-- 3. 页面里预留渲染元素和触发按钮 -->
<div id="captcha-element"></div>
<button id="captcha-button">注册</button>
<script>
var captcha;
window.initAliyunCaptcha({
SceneId: "你的场景ID",
mode: "popup", // popup 弹出式 / embed 嵌入式
element: "#captcha-element",
button: "#captcha-button",
success: function (captchaVerifyParam) {
// 关键:把 captchaVerifyParam 交给自己的后端去验签,别在这儿放行
doRegister(captchaVerifyParam);
},
fail: function (result) {
console.error(result); },
getInstance: function (instance) {
captcha = instance; },
slideStyle: {
width: 360, height: 40 }
});
</script>
第 3 步,服务端验签。 后端拿 CaptchaVerifyParam,调用验证码服务端的 VerifyIntelligentCaptcha 接口做校验,返回的 VerifyResult 为 true 才继续业务。
五个坑,我替你踩了
坑一:前端 success 回调不等于验证通过。 我最开始图省事,前端 success 了就直接走注册逻辑。这是个典型的"假安全"——脚本完全可以跳过前端 JS,直接构造请求打你的业务接口。正确姿势只有一条:业务放行必须以后端验签结果为准,而且验签失败要 fail-closed(拒绝),别写成"验签异常就放行"。
坑二:region 和服务端 Endpoint 必须同源。 客户端写着 cn,服务端却调了新加坡的地址,验证请求会直接报错。海外接入时客户端 region 用 sgp 或 ga,服务端就得用 captcha.ap-southeast-1.aliyuncs.com 或全球加速地址,两边是一个"国内/海外"的属性,不能混着用。另外用全球加速地址时,服务端拿到的源 IP 会是节点 IP,如果你后端还有基于真实 IP 的风控,这一点要提前评估。
坑三:验证码 JS 必须动态引入,不能本地部署。 官方文档明确说了,把 JS 拉下来自己托管、或者绕过动态加载,会导致验证码无法更新,安全能力失效,还可能误拦截、出兼容性问题。我一开始想"本地加载不是更快吗",好在看了文档没动手,否则又是一个白折腾的夜晚。
坑四:几个小参数不照做,行为很诡异。 滑块宽度建议最小 320px,写小于 320 的值系统会按 320 处理,所以别指望靠缩小滑块省点布局空间;拼图验证的图片尺寸和答案都是预设固定的,不要用 CSS 强行改样式,改了就是验证异常;初始化请求和验证请求之间的间隔要大于 2 秒,目的是让环境信息和图片资源采集完整;initAliyunCaptcha 除参数变化外不支持重复调用,同一页面也别重复引入 JS。
坑五:无痕验证不支持嵌入式。 无痕验证只能走 popup 模式,因为它需要"用户首次点击触发按钮"这个动作来发起判断。如果你想做那种一进页面就悄悄验证的效果,技术上做不到,老老实实绑一个按钮。
计费:别被"0 元开通"骗了,看清楚计费项
验证码 2.0 分按量和包年包月两种,也可以买资源包抵扣。我把关键项列出来,具体数字请以官方页面实时展示为准(产品价格会调整):
| 计费项 | 单价 | 说明 |
|---|---|---|
| 验证次数(中国内地) | 0.005 元/次 | 按天统计,次日出账 |
| 验证次数(非中国内地) | 0.007 元/次 | 海外接入 |
| 场景个数 | 5 元/个/天 | 前 3 个免费,第 4 个起计费 |
| 自定义策略配置 | 30 元/天 | 开启即计费 |
| 包年包月基础版 | 199 元/月、2388 元/年 | 分基础/标准/企业三档 |
| 资源包 | 9.9 元起 | 规格越大单价越低 |
两个容易忽略的点:一是按验证请求计费,不管这次请求有没有风险都算一次,所以别把无痕验证挂在首页这种高频入口上;二是"0 元开通"指的是按量付费可以零门槛开通,不是免费用,欠费超过 7 天实例会被释放、配置会被删除,回头得重新开通配置一遍。
按我这种一天几百次验证的量,一个月也就几块钱,比被人薅走优惠券便宜多了。
顺手把被薅最狠的那个接口也补上
我的教训其实不只注册接口。真正被薅得最惨的是短信——脚本疯狂调注册接口,每注册一次就触发一条验证码短信,一晚上短信费就烧掉小两百。
如果你的短信接口也暴露在外面,建议两边一起做:验证码负责"挡住机器",阿里云短信服务那边负责"兜底止血"。它自带防盗刷能力,可以开启验证码防盗刷监控,或者直接配验证码频控;还有智能发送管控,能设全局日发送量上限和单用户日频次阈值,异常消耗能压住;另外发送失败不计费,这点对成本控制挺友好。
这里有个前置条件得提前知道,免得白折腾:按签名实名制报备要求,国内短信签名报备基本需要企业资质,个人认证账号在这一步会卡住(个人开发者可以看看产品页上"短信认证"那条线)。我自己是先把验证码这道门堵上,短信频控随后补的。
怎么确认自己接对了
接入完成别只看页面弹没弹验证码,按这三处核对:
- 打开集成页面,看 Network 里初始化请求的返回,
Success为true; - 走一遍验证,验证请求里
VerifyResult为true; - 后端日志里看
VerifyIntelligentCaptcha的返回,VerifyResult为true。
最后做一次"坏人视角"测试:绕过前端,直接用 curl 打一遍你的业务接口。被挡住了,说明服务端验签真的生效了;要是还能注册成功,说明坑一你还没填。
从凌晨被短信吵醒,到后来脚本连注册都进不来,中间折腾了我两个晚上。现在回头看,这活儿的难点从来不是写代码——JS 那几行谁都会抄,难的是别自作聪明:别把验证放在前端、别自己托管 JS、别把参数瞎改。剩下的,交给风控模型去跟脚本赛跑就行了。