敏感接口防暴力破解,最稳妥的做法是把图形验证码作为风控触发后的二次验证,而不是对所有请求强制开启,同时必须配合服务端的频率限制。
先说结论:验证码只是增加攻击成本的手段,核心在于识别异常行为后再拦截,避免误伤正常用户。
- 先判断:确认哪些接口属于敏感操作(如登录、短信发送、密码修改)。
- 优先做:在网关或服务端实现基于 IP 或用户 ID 的原子性频率限制,这是第一道防线。
- 再验证:确保验证码的校验逻辑完全在服务端,且令牌使用后立即销毁。
核心架构与网关层防护
在业务代码之前,建议在 Nginx 网关层先做一层基础限流,拦截明显的高频恶意请求。以下是 Nginx 配置示例:
http {
# 定义限流区域,基于客户端 IP,10M 空间,每秒 5 个请求
limit_req_zone $binary_remote_addr zone=login_limit:10m rate=5r/s;
server {
location /api/login {
# 应用限流,允许突发 10 个请求,不延迟处理
limit_req zone=login_limit burst=10 nodelay;
limit_req_status 429;
proxy_pass http://backend;
}
}
}
配置生效后,超过阈值的请求将直接返回 429 状态码,减轻后端压力。
为什么需要组合策略
暴力破解的本质是自动化脚本高频尝试。图形验证码的作用是区分人类和机器,提高自动化成本。但如果所有请求都加验证码,会严重影响正常用户体验。业界安全实践表明,单一验证码难以完全阻止专业化破解,需结合多维风控。如果只靠验证码,攻击者可以通过打码平台或 OCR 技术绕过;如果只靠频率限制,可能会误伤共享 IP 的正常用户。两者结合才能在安全和体验之间取得平衡。
服务端落地实施
以下是具体的实施步骤,重点解决原子性与服务接入问题:
1. 界定敏感接口
梳理所有涉及身份认证和数据修改的接口,例如 /login、/sms/send、/password/reset。不要对所有接口开启,否则维护成本过高。
2. 原子性频率限制 (Redis + Lua)
使用 Redis 记录请求次数时,INCR 和 EXPIRE 分开执行非原子操作,高并发下可能导致计数不准。建议使用 Lua 脚本确保原子性。Java (Spring Boot) 示例:
@Autowired
private StringRedisTemplate redisTemplate;
private static final String LIMIT_SCRIPT =
"local key = KEYS[1] " +
"local limit = tonumber(ARGV[1]) " +
"local expire = tonumber(ARGV[2]) " +
"local current = redis.call('INCR', key) " +
"if current == 1 then redis.call('EXPIRE', key, expire) end " +
"if current > limit then return 1 else return 0 end";
public boolean checkRateLimit(String ip, int limit, int expireSeconds) {
String key = "rate_limit:" + ip;
Long result = redisTemplate.execute(
new DefaultRedisScript<Long>(LIMIT_SCRIPT, Long.class),
Collections.singletonList(key),
String.valueOf(limit),
String.valueOf(expireSeconds)
);
return result != null && result == 0; // 0 表示未超限
}
注意:过期时间要根据业务场景调整,短信接口通常比登录接口更严格。
3. 验证码服务选型与接入
可以选择自建或第三方服务。自建需确保图片生成不可预测,第三方需关注服务可用性。
- 极验 (Geetest):适合国内业务,支持滑块、点选。配置需关注
gt_server地址及 SDK 版本。 - reCAPTCHA:适合海外业务,需在 Google Console 获取 site_key 和 secret_key。
生成验证码后,将唯一标识(captcha_id)和答案哈希存入服务端缓存,设置较短过期时间(如 5 分钟)。
4. 后端校验与销毁
接收前端提交的 captcha_id 和 user_input。从缓存取出答案进行比对。比对成功后,立即删除该缓存条目,防止重放攻击。
// 校验成功后立即删除
redisTemplate.delete("captcha:" + captchaId);
5. 记录安全日志
记录触发验证码的 IP、用户 ID、时间戳和结果。这些日志用于后续分析攻击来源和调整阈值。
验证与测试
上线后需要通过以下方式确认防护是否起作用:
- 网关限流测试:使用
ab或curl循环请求,观察是否返回 429。for i in {1..20}; do curl -o /dev/null -s -w "%{http_code}\n" http://your-domain/api/login; done - 验证码逻辑测试:在测试环境连续多次触发失败,观察是否返回需要验证码的标识,且验证码错误时是否阻断操作。
- 重放攻击测试:捕获一个正确的验证码请求,尝试重复提交,确认服务端是否拒绝第二次使用(应返回验证码失效错误)。
常见坑与排查
- 前端校验陷阱:千万不要只在前端判断是否显示验证码,攻击者可以直接绕过前端调用接口。所有逻辑必须在服务端。
- 验证码复用:同一个验证码答案只能使用一次,使用后立即失效。排查时可查看 Redis 键是否在校验后被删除。
- OCR 识别风险:过于简单的字符验证码容易被 OCR 识别,建议采用滑块、点选等交互型验证码。
- 误伤正常用户:阈值设置过低会导致正常用户频繁遇到验证码,建议初期设置较宽松,根据安全日志逐步收紧。
- Redis 连接超时:高并发下 Redis 连接池可能耗尽,导致限流逻辑失效(默认放行)。建议配置合理的连接池大小及超时降级策略。