ThinkPHP如何防止恶意爬虫_恶意爬虫防御配置【安全】
ThinkPHP防爬虫需组合中间件过滤、行为特征分析与动态验证码,并启用CSRF令牌。UA白名单易被伪造,仅作快速过滤;验证码应在高风险前置触发,结合时间戳与访问频率判断脚本行为,同时可加入IP限流和请求间隔检测。
先说几个核心判断:单纯靠 User-Agent 白名单防爬虫,在 ThinkPHP 这类框架里基本等于“形同虚设”。UA 这玩意儿,谁都能伪造,拿来当安全屏障,敌人只要照抄几个合法字符串就能轻松绕过。它的正确用法,其实是作为第一道快速过滤——放行那些明显可信的客户端,或者标记一下异常流量,比如 User-Agent 字段是空的、纯数字的、或者里面直接挂着 python-requests、curl 这些标识的请求。仅此而已。
真正的防御链条,需要串起中间件统一过滤、行为特征分析、以及动态验证码触发机制,同时别忘了把 CSRF 令牌这套防护老老实实加上。
中间件做 UA 过滤,别把脏活丢给控制器
很多开发者习惯在控制器里顺手写一句 if (stripos($ua, 'Chrome') !== false) 之类的判断。这不是个好习惯。控制器是处理业务逻辑的地方,不是写流量筛子的。代码复用难、后续升级排查都是坑。
正确的做法是写一个中间件,比如 app/middleware/UserAgentFilter.php,在 handle() 方法里统一处理。有几个细节值得注意:
- 取值要用
request()->header('user-agent'),这是 ThinkPHP 6+ 推荐的方式。别直接去读$_SERVER['HTTP_USER_AGENT'],否则在 Swoole 或 FastCGI 模式下,这个变量可能为空,坑你没商量。 - 白名单配置建议放在
app/extra/spider.php这类配置文件中,比如return ['Mobile Safari', 'Chrome', 'WeChat'];。千万别在中间件里硬编码,也别去查数据库——首字节响应时间经不起这种折腾。 - 匹配方式务必用
stripos(),而不是==或者正则全匹配。合法的 User-Agent 变体太多了,iPhone 和 iPad 的 UA 除了平台字段不同,其他几乎一样,你很难写个精确匹配覆盖所有情况。 - 对那些空字符串、纯数字(比如
123456),或者 UA 里带着curl、httpie、python-requests的请求,建议记录日志并触发限流。但别一刀切直接用halt()打死,误伤正常用户的代价可能更大。
UA 白名单不能当安全屏障,这是常识
说白了,User-Agent 就是一个 HTTP 头,客户端想怎么写就怎么写。Scrapy、Playwright、Requests 这些工具,默认就会随机生成 UA,甚至能完美复刻最新版 Chrome 的 User-Agent。你加的白名单,敌人只要照抄一行字符串就能绕过。
- 试试这条命令:
curl -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"。瞧瞧,你的“UA 白名单”是不是直接被打穿了? - 真正该盯住的,是那些 UA 看起来合法,但行为明显异常的请求:没有
Cookie、没有Referer、X-Requested-With字段为空、或者缺少 JS 行为相关的字段。举个例子,一个请求从页面加载到点击某个按钮的间隔,如果小于 800 毫秒,基本可以断定是脚本模拟的。 - 检查一下
request()->cookie('thinkphp_token')是否存在且未过期。真实的用户访问,通常会带着会话标识;而绝大多数爬虫,连维护一个 Cookie jar 都懒得做。
验证码触发点,别只卡在“提交”那个按钮上
人机对抗这件事,难点不是“要不要加验证码”,而是“在哪个环节加、对谁加”。ThinkPHP 自带的 captcha 扩展,只提供了生成和验证的函数,它不会替你决定什么时候该弹验证码。
可行的思路是这样:
- 首次访问页面时,通过 Ja vaScript 注入一个隐藏字段,比如
_ts(时间戳)。服务端收到请求后,比对request()->post('_ts')和当前时间的差值。如果小于 800 毫秒,基本可以判定是脚本在提交。 - 对于高风险动作,比如登录、注册、批量导出数据,要做前置的风险评分。根据评分结果,动态决定是否需要弹验证码,而不是固定路径强制触发。
- 验证码不应该只和“提交按钮”绑定。更合理的做法是,从页面加载那一刻起,就开始收集用户的行为信号。比如鼠标轨迹、页面停留时间、滚动行为等等。这些数据比一个简单的验证码要可靠得多。
CSRF 防护,不能光靠文档说“开启”
表单防恶意提交,光指望 UA 或者 Referer 是不靠谱的。ThinkPHP 的 CSRF 防护依赖令牌机制,而且必须显式启用,效果才会到位。
- 在模板中使用
@csrf指令,它会自动生成。这是基础操作。 - 确保应用已经开启了框架自带的 CSRF 中间件。默认情况下,ThinkPHP 6 是开启的,但如果你自定义了路由,或者对 API 分组做了特殊配置,有可能这个中间件被绕过去了。需要手动检查并确认。
- 这里有个容易忽略的点:单纯的 POST 请求并不等于“安全”。攻击者照样可以伪造一个 POST 请求,并且带上合法的 token。所以,token 的生成和验证必须绑定 session + 时间戳 + 随机因子,确保每个 token 都是唯一的、一次性的、有有效期的。

说到底,UA 白名单一旦写死,反而容易成为攻击者的路标——他们只需要绕开那几个关键词就行。而验证码,如果只在提交时刻才校验,等于你把门锁装在了用户已经进门之后。真正的防御,是一套组合拳:从请求进门的第一道过滤,到行为层面的持续分析,再到关键环节的动态验证。缺一环,效果都会大打折扣。


































