在 Nginx 的 `server` 块中配置以拒绝 `curl`、`python` 等工具发起的请求,只允许浏览器正常请求,核心策略是**识别并拦截非浏览器类请求的 `User-Agent` 头**,并结合其他防护措施进行综合限制 [ref_5][ref_6]。以下是具体配置方法、示例及深度解析。
### **核心配置策略与实现**
浏览器请求通常会携带包含 `Mozilla`、`AppleWebKit`、`Chrome`、`Safari`、`Edge` 等标识的 `User-Agent` 字符串,而命令行工具(如 `curl`、`wget`)或编程语言库(如 `python-requests`、`java`)的 `User-Agent` 则具有明显不同的特征。Nginx 可以通过 `$http_user_agent` 变量来获取并匹配这些特征。
#### **1. 基础配置:使用 `if` 指令与 `map` 指令拦截非浏览器 UA**
**方案A:直接在 `server` 块中使用 `if` 判断(适用于简单场景)**
此方法直接在 `location` 或 `server` 上下文中拒绝特定 `User-Agent`。
```nginx
server {
listen 80;
server_name your_domain.com;
# 根路径或通用location块
location / {
# 定义拒绝的User-Agent模式
if ($http_user_agent ~* (curl|wget|python-requests|java|Go-http-client|Apache-HttpClient|PostmanRuntime)) {
return 403; # 直接返回403禁止访问
# 或者返回其他错误码或重定向
# return 444; # Nginx特有的立即关闭连接
}
# 允许浏览器请求继续处理
proxy_pass http://backend_server;
# 或其他正常处理指令...
}
}
```
* **`~*`** 表示不区分大小写的正则匹配。
* 匹配模式 `(curl|wget|python-requests|... )` 可以根据需要扩展,常见需要拦截的 `User-Agent` 还包括 `python-urllib`、`Java/`、`node-fetch`、`okhttp` 等 [ref_5]。
* **风险提示**:过度使用 `if` 在 `location` 上下文中可能影响性能,且 `if` 在非标准上下文中存在一些众所周知的注意事项(俗称“if is evil”)。但对于简单的 UA 拦截,在可控范围内使用是常见做法。
**方案B:使用 `map` 指令定义更复杂的 UA 映射(推荐用于多条件管理)**
`map` 指令将变量映射为新变量,逻辑更清晰,易于维护。
```nginx
# 在http块中定义map映射(通常在nginx.conf的http部分)
http {
map $http_user_agent $is_non_browser {
default 0; # 默认值0,表示浏览器
# 匹配非浏览器UA时,将变量$is_non_browser设置为1
~*(curl|wget|python-requests|java|Go-http-client|Apache-HttpClient|PostmanRuntime) 1;
~*(python-urllib|libwww-perl|node-fetch|okhttp|http\.rb) 1;
# 可以添加空UA或明显异常的UA拦截
~*^$ 1; # 拦截空User-Agent
~*(scan|bot|crawl|spider) 1; # 拦截常见爬虫标识(注意可能误伤合法爬虫)
}
server {
listen 80;
server_name your_domain.com;
location / {
# 使用map生成的变量进行判断
if ($is_non_browser) {
return 403;
# 或记录日志后拒绝
# access_log /var/log/nginx/blocked_ua.log;
# return 444;
}
proxy_pass http://backend_server;
# 其他配置...
}
}
}
```
此方法将 UA 判断逻辑与业务逻辑分离,更优雅且性能更优 [ref_3]。
#### **2. 增强配置:结合 `robots.txt` 与访问频率限制**
仅靠 `User-Agent` 拦截可能被轻易伪造绕过。应结合其他规则进行纵深防御。
**结合 `robots.txt` 声明(辅助性,对遵守规则的爬虫有效)**
首先,确保网站根目录下存在 `robots.txt` 文件,禁止所有爬虫或特定爬虫访问敏感区域。
```nginx
location /robots.txt {
alias /path/to/your/robots.txt; # 指向实际的robots.txt文件
# 或者直接返回内容
# add_header Content-Type text/plain;
# return 200 "User-agent: *\nDisallow: /\n";
}
```
`robots.txt` 内容示例:
```
User-agent: *
Disallow: /
```
此文件声明不欢迎所有爬虫,但**这只是一个声明,不具强制约束力**,需与 Nginx 的强制拦截配合 [ref_5]。
**添加访问频率限制(Rate Limiting)**
许多自动化脚本会高频请求,可以通过 `limit_req` 模块限制同一 IP 的请求速率。
```nginx
http {
# 定义一个名为one的限流区,每秒10个请求,突发队列为5
limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s;
server {
listen 80;
server_name your_domain.com;
location / {
# 应用限流,突发请求延迟处理
limit_req zone=one burst=5 nodelay;
# UA拦截规则
if ($is_non_browser) {
return 403;
}
proxy_pass http://backend_server;
}
# 对特定敏感路径(如登录、API)实施更严格的限流
location /api/ {
limit_req zone=one burst=2 nodelay;
# ... 其他配置
}
}
}
```
#### **3. 高级配置:利用 `$http_sec_fetch_*` 头进行浏览器环境验证(现代浏览器)**
现代浏览器(Chrome 76+, Firefox 90+, Edge 79+, Safari 14+)在发起跨域或特定类型请求时,会自动添加 `Sec-Fetch-*` 系列请求头,如 `Sec-Fetch-Site`、`Sec-Fetch-Mode`、`Sec-Fetch-Dest`、`Sec-Fetch-User`。非浏览器环境(如 `curl`、`python`)通常不会发送这些头,可以利用此特性进行更精准的浏览器验证 [ref_6]。
```nginx
server {
listen 80;
server_name your_domain.com;
location / {
# 检查是否存在Sec-Fetch-Dest头(现代浏览器基本都会发送)
if ($http_sec_fetch_dest = "") {
# 如果不存在该头,很可能是非浏览器请求
return 403;
}
# 更严格的检查:要求请求来自同站(same-site)且是导航请求(navigate)
# 这能拦截大部分非浏览器发起的直接API调用
if ($http_sec_fetch_site != "same-origin" && $http_sec_fetch_site != "same-site") {
if ($http_sec_fetch_mode != "navigate" && $http_sec_fetch_mode != "nested-navigate") {
return 403;
}
}
proxy_pass http://backend_server;
}
}
```
**注意**:此方法依赖于客户端支持 Fetch Metadata 标准。虽然能有效拦截大量简单脚本,但**不能作为唯一防线**,因为:
1. 旧版浏览器不支持。
2. 恶意脚本可以伪造这些头部。
3. 合法的 API 调用(如前端 AJAX)也可能被误伤,需根据实际情况调整规则。
### **综合配置示例与测试**
以下是一个整合了上述多种策略的 `server` 块配置示例:
```nginx
http {
# 定义限流区
limit_req_zone $binary_remote_addr zone=perip_zone:10m rate=5r/s;
# 定义非浏览器UA映射
map $http_user_agent $is_blocked_ua {
default 0;
~*(curl|wget|python-requests|java|Go-http-client|Apache-HttpClient|PostmanRuntime|python-urllib|libwww-perl|node-fetch|okhttp) 1;
~*^$ 1; # 空UA
~*(scan|bot|crawl|spider|Bot) 1; # 注意大小写
}
server {
listen 80;
server_name example.com;
root /var/www/html;
# 统一记录被拦截的请求到特定日志
log_format blocked '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent"';
access_log /var/log/nginx/blocked_access.log blocked if=$is_blocked_ua;
location / {
# 1. 应用访问频率限制
limit_req zone=perip_zone burst=10 nodelay;
# 2. 基于User-Agent的拦截
if ($is_blocked_ua) {
# 返回403前,可记录到上述专用日志
return 403 "Access Denied: Automated tools are not allowed.";
}
# 3. (可选)基于Sec-Fetch头的增强验证
# 如果希望更严格,可启用以下检查
# if ($http_sec_fetch_dest = "") {
# return 403;
# }
# 4. 正常请求的处理
try_files $uri $uri/ /index.html;
# 或代理到后端
# proxy_pass http://backend;
# 安全头设置(增强浏览器安全性)
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
}
# 显式处理robots.txt
location = /robots.txt {
alias /var/www/html/robots.txt;
}
# 对API接口可以应用更严格的规则
location /api/ {
limit_req zone=perip_zone burst=2 nodelay;
# API可能允许一些自动化工具,此处规则可以不同
# 例如,仅允许带有有效API密钥的请求,而不完全依赖UA
# ...
}
}
}
```
### **配置验证与测试方法**
配置完成后,执行以下步骤验证:
1. **检查配置文件语法**:
```bash
nginx -t
```
确保输出 `syntax is ok` 和 `test is successful`。
2. **重载 Nginx 配置**:
```bash
nginx -s reload # 或 systemctl reload nginx
```
3. **使用 `curl` 和浏览器进行测试**:
* **测试被拦截**:使用 `curl` 命令应收到 403 错误。
```bash
curl -I http://example.com/
# 预期返回: HTTP/1.1 403 Forbidden
```
* **测试伪造 UA**:尝试使用 `curl` 伪造浏览器 UA,应同样被拦截(如果配置了 `Sec-Fetch` 头检查,则伪造 UA 可能仍会被拦截)。
```bash
curl -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" http://example.com/
```
* **测试浏览器允许**:使用 Chrome、Firefox 等浏览器访问网站,应能正常加载页面。
* **测试 `python` 请求**:使用 `requests` 库的脚本也应被拦截。
```python
import requests
response = requests.get('http://example.com')
print(response.status_code) # 预期输出 403
```
### **策略对比与注意事项**
下表总结了不同拦截方法的优缺点:
| 方法 | 原理 | 优点 | 缺点 | 建议 |
| :--- | :--- | :--- | :--- | :--- |
| **User-Agent 匹配** | 拦截已知的非浏览器 UA 字符串。 | 配置简单,直接有效,能拦截大部分常见工具。 | 易被伪造绕过(`curl -A "浏览器UA"`)。需维护 UA 列表。 | **基础必备**,作为第一道防线。 |
| **`robots.txt` 声明** | 声明网站不希望被爬取的规则。 | 标准合规,对遵守规则的爬虫有效。 | 无强制力,恶意爬虫会忽略。 | **辅助使用**,表明态度,减少善意爬虫压力。 |
| **访问频率限制** | 限制同一 IP 的请求速率。 | 能有效减缓爬虫和 CC 攻击速度。 | 可能误伤共用出口 IP 的正常用户。 | **强烈推荐**,与 UA 拦截互补。 |
| **`Sec-Fetch-*` 头验证** | 检查现代浏览器特有的请求头。 | 难以伪造(需客户端实现支持),准确率高。 | 旧浏览器不支持,可能误伤合法 API 调用。 | **高级增强**,用于对安全性要求极高的场景。 |
**重要注意事项**:
1. **误伤风险**:过于严格的 UA 规则可能拦截合法的浏览器或搜索引擎爬虫(如 Googlebot)。在定义拦截列表时,应避免匹配过于宽泛的关键字(如 `bot` 可能误伤 `Googlebot`)。可以考虑结合 `allow` 规则对已知合法的 UA 放行。
2. **性能影响**:复杂的正则匹配和过多的 `if` 指令可能影响 Nginx 性能。尽量使用 `map` 指令,并将复杂匹配放在 `http` 块而非 `server` 或 `location` 块中。
3. **防御绕过**:任何基于请求头的防御都可以被足够复杂的客户端伪造。因此,**不应将此作为唯一的安全措施**。对于核心 API 或敏感操作,必须实施基于令牌(Token)、签名、OAuth 等强认证机制 [ref_2]。
4. **日志监控**:务必配置并定期检查拦截日志(如示例中的 `blocked_access.log`),分析攻击模式,并据此更新拦截规则。
综上所述,在 Nginx `server` 块中实现拒绝非浏览器请求的**最佳实践是组合策略**:以 **`User-Agent` 拦截为基础**,辅以**访问频率限制**,对现代浏览器环境可选择性启用 **`Sec-Fetch-*` 头验证**,同时通过 **`robots.txt` 进行合规声明**。配置完成后需充分测试,并建立日志监控机制以持续优化规则 [ref_1][ref_3][ref_5][ref_6]。