不可靠。$_SESSION 绑定 session ID 而非 IP,无法准确统计同一 IP 下多用户或不同 IP 共享 session 的情况;正确做法是结合可信代理白名单解析 X-Forwarded-For 获取真实 IP,并用 Redis 原子操作限频。

用 $_SESSION 记录 IP 提交次数是否可靠?
不可靠。PHP 的 $_SESSION 默认绑定到客户端的 session ID(通常存在 cookie 里),而同一个 IP 下多个用户可能共享 session ID(比如公用电脑、代理后端),也可能不同 IP 却因 cookie 复用导致误判。单纯靠 $_SESSION 统计“IP 提交次数”会漏判或误封,必须配合真实 IP 提取 + 服务端持久存储。
如何正确获取并校验客户端真实 IP?
不能直接用 $_SERVER['REMOTE_ADDR'],它常被反向代理、CDN 或负载均衡覆盖。需按优先级检查可信头字段,并限定可信代理列表:
- 先确认你的 Web 服务器(如 Nginx)是否已配置
real_ip_header X-Forwarded-For和set_real_ip_from指令 - PHP 中应使用类似逻辑:
$ip = $_SERVER['REMOTE_ADDR']; if (isset($_SERVER['HTTP_X_FORWARDED_FOR']) && !empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { $ips = array_map('trim', explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])); // 只取第一个非私有 IP(假设你信任最外层代理) foreach ($ips as $candidate) { if (!filter_var($candidate, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) { continue; } $ip = $candidate; break; } } - 忽略
X-Real-IP或X-Forwarded-For未加白名单校验的场景,否则极易被伪造
用 Redis 实现每 IP 每小时最多 5 次提交
推荐用 Redis 的 INCR + EXPIRE 原子操作,避免并发写入冲突。Key 设计为 form:submit:ip:{sha256($ip)}:hour,防止 IP 字符串含特殊字符或过长:
// 连接 Redis(示例用 predis)
$client = new Predis\Client();
$key = 'form:submit:ip:' . hash('sha256', $ip) . ':hour';
$limit = 5;
$ttl = 3600;
$count = $client->incr($key);
if ($count == 1) {
$client->expire($key, $ttl);
}
if ($count > $limit) {
die('提交过于频繁,请稍后再试');
}
- 不用 MySQL 是因为高并发下
INSERT ... ON DUPLICATE KEY UPDATE或事务查+增易锁表 - 不建议用文件存储:多进程写入竞争、无自动过期、I/O 瓶颈明显
- Key 中哈希 IP 是为了规避 Redis key 名称规则限制(如 IPv6 冒号、斜杠等)
为什么不能只依赖前端 JS 或隐藏域防重复?
所有客户端控制都形同虚设。用户禁用 JS、手动构造 POST 请求、用 curl / Postman 都能绕过。真正有效的频次控制必须:
立即学习“PHP免费学习笔记(深入)”;
- 在 PHP 接收请求后、业务逻辑执行前完成校验
- 校验失败立即
exit或返回 429 状态码,不进入后续处理 - 记录日志(如 IP、时间、User-Agent)用于人工复核异常模式
复杂点在于代理链识别和 Redis 连接容错——如果 Redis 不可用,应降级为宽松策略(比如只记内存数组,或跳过限频),而不是让整个表单不可用。











