在 Web 应用中,IP 访问限制和提交频率控制是防止恶意攻击(如暴力破解、DoS 攻击、API 滥用等)的常见防护手段。通过合理的限制策略,可以有效保护服务器资源,提升应用安全性。本文将介绍如何结合 Nginx 和 PHP,利用反向代理传递真实客户端 IP 并在后端进行请求频率控制,防止攻击者绕过限制,确保应用的稳定性和安全性。大量并发请求虽然能加快响应速度,但如果不加以限制,极易造成服务器负载急剧上升,影响服务稳定性。通过后端的限流策略,可以有效防止服务器过载。
案例
假设我们在前端使用 fetch 方法,通过 Promise.all 并发发起 2000 个请求,代码如下:
// 创建包含 2000 个请求 Promise 的数组
const requestPromises = Array.from({ length: 2000 }, (_, index) => {
return fetch('/demoapi') // 模拟向同一个 API 发起 200 个请求
.then(response => {
if (!response.ok) {
throw new Error('Request failed');
}
return response.json();
})
.catch(error => {
console.error('Error in request ' + index + ':', error);
});
});
// 并发发起所有请求
Promise.all(requestPromises)
.then(responses => {
console.log('All requests completed successfully:', responses);
})
.catch(error => {
console.error('Some requests failed:', error);
});
如果服务器没有进行适当的请求限制,这样大规模的并发请求将导致以下问题:
服务器处理能力被迅速消耗:
一次性发起 2000 个请求,意味着服务器需要在短时间内处理 2000 个连接请求,消耗大量的 CPU、内存和带宽资源。
如果服务器配置较低或处理能力有限,这种情况下服务器的资源将被快速消耗,导致响应时间延长,甚至拒绝新的连接请求。
服务不可用或崩溃:
在负载高峰期,服务器无法及时响应所有请求,导致一些请求返回错误(例如 500 Internal Server Error 或 503 Service Unavailable)。
如果服务器有硬件或软件限制(如连接数限制、带宽限制),服务可能会宕机或无法响应更多的请求。
其他用户的正常请求被阻塞:
服务器资源被大量占用后,正常用户的请求也会受到影响,可能无法及时得到响应,导致用户体验极差。
方案
一、 使用 Nginx 限制 IP 访问频率
Nginx 提供了 limit_req 和 limit_conn 模块,可以限制客户端的请求频率和连接数。这些模块能有效防止暴力破解、DDOS 等攻击。
1.1 配置 Nginx 请求速率限制
使用 limit_req_zone 来限制每个 IP 的请求频率。例如,限制每秒最多 1 次请求,并允许突发 5 个请求:
http {
# 定义共享内存区域,限制每个 IP 的请求速率
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
server {
location / {
# 应用速率限制
limit_req zone=one burst=5 nodelay;
proxy_pass http://your_backend;
}
}
}
rate=1r/s 表示每个 IP 每秒最多发送 1 个请求。
burst=5 允许在短时间内最多突发 5 个请求。
nodelay 表示超过限制的请求将立即拒绝,而不是排队等待。
1.2 配置 Nginx 连接数限制
使用 limit_conn 来限制每个 IP 的并发连接数,例如限制每个 IP 最多 10 个连接:
http {
# 定义共享内存区域,限制每个 IP 的连接数
limit_conn_zone $binary_remote_addr zone=addr:10m;
server {
location / {
# 应用连接数限制
limit_conn addr 10;
proxy_pass http://your_backend;
}
}
}
limit_conn addr 10 表示每个 IP 同时最多只能建立 10 个连接。
三、 在 PHP 中实现提交次数控制
为了防止恶意用户频繁提交数据或发起暴力攻击,你可以在 PHP 中实现更细致的请求次数控制。常见的方案是使用 Redis 或 Memcached 这样的缓存系统,记录 IP 或用户的请求频次。
2.1 使用 PHP 和 Redis 记录请求次数
可以通过 Redis 记录每个 IP 的请求次数,并设置过期时间来限制请求频率:
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$ip = $_SERVER['REMOTE_ADDR'];
$limit = 100; // 设置限制为每小时 100 次请求
$expire = 3600; // 过期时间为 1 小时
// 检查当前 IP 的请求次数
$count = $redis->get($ip);
if ($count === false) {
// 如果没有记录,则初始化为 1 并设置过期时间
$redis->set($ip, 1, $expire);
} elseif ($count < $limit) { // 如果请求次数未超限,递增计数 $redis->incr($ip);
} else {
// 如果超限,返回错误响应
http_response_code(429);
echo '请求过于频繁,请稍后再试。';
exit;
}
通过 Redis::incr() 来递增请求计数。
如果请求次数达到限制,可以返回 HTTP 状态码 429 Too Many Requests。
如果 PHP 无法获取 REMOTE_ADDR,可能是由于代理服务器(如 Nginx)配置了负载均衡或反向代理,导致 PHP 直接读取到的是代理服务器的 IP,而不是客户端的真实 IP。在这种情况下,可以通过正确配置 Nginx 和 PHP,使 REMOTE_ADDR 获取到客户端的真实 IP。
通过 Nginx 转发客户端真实 IP
Nginx 作为反向代理时,可以使用 X-Forwarded-For 头部传递客户端的真实 IP。确保在 Nginx 配置中添加以下内容,将客户端的 IP 传递给后端:
配置 Nginx 传递客户端的真实 IP
通过 Nginx 转发客户端真实 IP
http {
# 设定通过 X-Forwarded-For 传递客户端的真实 IP
set_real_ip_from 0.0.0.0/0; # 信任的代理服务器的 IP 地址范围
real_ip_header X-Forwarded-For;
server {
location / {
proxy_pass http://your_backend;
# 向后端 PHP 服务器传递客户端 IP
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
}
set_real_ip_from 指定哪些 IP 地址可以信任并使用其 X-Forwarded-For 头中的信息。这里 0.0.0.0/0 表示信任所有 IP。
real_ip_header 设置用于表示真实客户端 IP 的 HTTP 头部字段,通常使用 X-Forwarded-For
在 PHP 中,可以从 $_SERVER[‘X-Forwarded-For’] 获取客户端的真实 IP。可以根据具体情况修改代码:
1.2 修改 PHP 读取 X-Forwarded-For
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ip_addresses = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
$ip = trim(end($ip_addresses)); // 获取最后一个非空的 IP
} else {
$ip = $_SERVER['REMOTE_ADDR']; // 如果没有 X-Forwarded-For,使用 REMOTE_ADDR
}
使用 real_ip_recursive
如果 Nginx 经过多个代理层,则可以使用 real_ip_recursive 来确保获取最终客户端的真实 IP。
使用 real_ip_recursive
http {
set_real_ip_from 0.0.0.0/0;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
server {
location / {
proxy_pass http://your_backend;
}
}
}
real_ip_recursive on 表示 Nginx 会递归地检查所有的 X-Forwarded-For 头部,从最后一个信任的代理 IP 开始提取真实的客户端 IP。
四、 总结
一次性发起大量并发请求虽然能加快响应速度,但如果不加以限制,极易造成服务器负载急剧上升,影响服务稳定性。通过后端的限流策略,可以有效防止服务器过载。