优化更新
This commit is contained in:
@@ -7,8 +7,6 @@ CORS(Cross-Origin Resource Sharing)跨域资源共享是前后端分离架
|
||||
| 环境变量 | 默认值 | 说明 |
|
||||
|---------|--------|------|
|
||||
| `CORS_ALLOWED_ORIGINS` | 空(允许所有源) | 允许的跨域源(逗号分隔) |
|
||||
| `TRUSTED_PROXY_COUNT` | `0` | 可信代理层数 |
|
||||
| `TRUSTED_PROXY_IPS` | 空 | 可信代理 IP 白名单(逗号分隔) |
|
||||
|
||||
## 配置示例
|
||||
|
||||
@@ -20,10 +18,6 @@ CORS_ALLOWED_ORIGINS=
|
||||
|
||||
# 允许指定源(生产环境)
|
||||
CORS_ALLOWED_ORIGINS=https://admin.example.com,https://www.example.com
|
||||
|
||||
# 信任一层反向代理(Nginx)
|
||||
TRUSTED_PROXY_COUNT=1
|
||||
TRUSTED_PROXY_IPS=127.0.0.1
|
||||
```
|
||||
|
||||
:::warning 生产环境
|
||||
@@ -73,28 +67,6 @@ register_cors(app)
|
||||
CORS → 限流 → 上传体积限制 → 操作日志 → DB会话 → Redis → 业务逻辑
|
||||
```
|
||||
|
||||
## 代理头处理
|
||||
|
||||
当应用部署在 Nginx 等反向代理后,需要配置代理信任:
|
||||
|
||||
```bash
|
||||
# 信任一层代理
|
||||
TRUSTED_PROXY_COUNT=1
|
||||
TRUSTED_PROXY_IPS=127.0.0.1
|
||||
```
|
||||
|
||||
`TRUSTED_PROXY_COUNT` 说明:
|
||||
|
||||
| 值 | 说明 |
|
||||
|----|------|
|
||||
| `0` | 不信任 X-Forwarded-For,使用 request.client.host |
|
||||
| `1` | 信任一层反向代理 |
|
||||
| `2` | 信任两层代理 |
|
||||
|
||||
:::tip 安全提示
|
||||
仅当应用部署在反向代理后才设置 `TRUSTED_PROXY_COUNT>=1`。如果应用被直接暴露(如 Docker bridge 映射端口),设置代理信任会导致客户端伪造 XFF 绕过限流。
|
||||
:::
|
||||
|
||||
## 总结
|
||||
|
||||
CORS 跨域配置具备以下特点:
|
||||
@@ -102,7 +74,6 @@ CORS 跨域配置具备以下特点:
|
||||
```
|
||||
1. 环境变量配置:通过 CORS_ALLOWED_ORIGINS 灵活配置允许的源
|
||||
2. 开发/生产分离:开发环境允许所有源,生产环境指定域名
|
||||
3. 代理头支持:TRUSTED_PROXY_COUNT 配置代理信任层数
|
||||
4. 安全默认:未配置时允许所有源但不携带凭证
|
||||
5. 最外层注册:确保所有响应都带 CORS 头
|
||||
3. 安全默认:未配置时允许所有源但不携带凭证
|
||||
4. 最外层注册:确保所有响应都带 CORS 头
|
||||
```
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
▼
|
||||
┌──────────────────────────────────────────────────────┐
|
||||
│ get_client_ip(request) │
|
||||
│ ├─ 直连对端命中可信代理白名单? │
|
||||
│ │ ├─ 是 → 从 X-Forwarded-For 取真实客户端 IP │
|
||||
│ │ └─ 否 → request.client.host(fail-closed) │
|
||||
│ ├─ X-Forwarded-For 存在? │
|
||||
│ │ ├─ 是 → 取最左侧 IP(真实客户端 IP) │
|
||||
│ │ └─ 否 → request.client.host │
|
||||
│ └─ client 为 None → 回退 127.0.0.1 │
|
||||
└──────────┬───────────────────────────────────────────┘
|
||||
│
|
||||
@@ -53,11 +53,8 @@ def get_client_ip(request) -> str:
|
||||
"""
|
||||
获取客户端真实IP地址
|
||||
|
||||
仅当直连对端命中 TRUSTED_PROXY_IPS 白名单且 TRUSTED_PROXY_COUNT>0 时才信任
|
||||
X-Forwarded-For 头:
|
||||
- TRUSTED_PROXY_COUNT==0 或 TRUSTED_PROXY_IPS 未配置 / 对端不命中:不信任代理头,
|
||||
直接使用 request.client.host(fail-closed,防止被直接暴露时伪造 XFF)
|
||||
- 命中白名单:从 X-Forwarded-For 右侧取倒数第 N 个 IP 作为真实客户端IP
|
||||
优先从 X-Forwarded-For 头取最左侧IP(即真实客户端IP),
|
||||
无代理头时回退到 request.client.host。
|
||||
"""
|
||||
```
|
||||
|
||||
@@ -65,60 +62,41 @@ def get_client_ip(request) -> str:
|
||||
|
||||
### 代理链解析原理
|
||||
|
||||
当应用部署在 Nginx/HAProxy 等反向代理之后,`request.client.host` 拿到的是代理 IP 而非真实客户端 IP。此时需要从 `X-Forwarded-For`(XFF)头提取真实 IP。
|
||||
当应用部署在 Nginx/CDN 等反向代理之后,`request.client.host` 拿到的是代理 IP 而非真实客户端 IP。此时从 `X-Forwarded-For`(XFF)头取最左侧 IP 即为真实客户端 IP。
|
||||
|
||||
```
|
||||
客户端 (1.2.3.4)
|
||||
│
|
||||
▼
|
||||
外层代理 (10.0.0.1) ← 追加 XFF: 1.2.3.4
|
||||
CDN (10.0.0.1) ← 追加 XFF: 1.2.3.4
|
||||
│
|
||||
▼
|
||||
内层代理 (10.0.0.2) ← 追加 XFF: 1.2.3.4, 10.0.0.1
|
||||
Nginx ← 透传 XFF: 1.2.3.4(不追加)
|
||||
│
|
||||
▼
|
||||
FastAPI 应用
|
||||
request.client.host = 10.0.0.2
|
||||
X-Forwarded-For = "1.2.3.4, 10.0.0.1"
|
||||
get_client_ip → 取最左侧 → "1.2.3.4"
|
||||
```
|
||||
|
||||
**取值逻辑**:XFF 最右侧由受信代理追加,客户端可控的伪造条目只可能出现在左侧。跳过最右侧 `TRUSTED_PROXY_COUNT` 个代理条目,取真实客户端 IP。
|
||||
**取值逻辑**:XFF 最左侧由 CDN/反代追加的真实客户端 IP,右侧为代理节点 IP。直接取最左侧即可。
|
||||
|
||||
### 安全设计:fail-closed
|
||||
:::warning Nginx 配置注意
|
||||
Nginx 代理配置应使用 `$http_x_forwarded_for`(透传上游值)而非 `$proxy_add_x_forwarded_for`(会追加本机 IP),避免 XFF 条目数不可控:
|
||||
|
||||
```nginx
|
||||
proxy_set_header X-Forwarded-For $http_x_forwarded_for;
|
||||
```
|
||||
:::
|
||||
|
||||
### 降级策略
|
||||
|
||||
| 场景 | 行为 |
|
||||
|------|------|
|
||||
| `TRUSTED_PROXY_COUNT=0` | 不信任任何代理头,直接返回 `request.client.host` |
|
||||
| `TRUSTED_PROXY_IPS` 未配置或为空 | 同上,fail-closed |
|
||||
| 直连对端不在白名单中 | 同上(防止应用被直接暴露时客户端伪造 XFF) |
|
||||
| 对端命中白名单 + XFF 条目少于代理层数 | 回退到 XFF 最右侧条目(避免拿到代理 IP) |
|
||||
| X-Forwarded-For 存在 | 取最左侧 IP |
|
||||
| X-Forwarded-For 为空 | 返回 `request.client.host` |
|
||||
| `request.client` 为 None | 回退到 `127.0.0.1` |
|
||||
|
||||
### 代理配置
|
||||
|
||||
在 `.env` 中配置可信代理:
|
||||
|
||||
```bash
|
||||
# 信任的代理层数(0 = 不信任代理头)
|
||||
TRUSTED_PROXY_COUNT=1
|
||||
|
||||
# 可信代理IP白名单(逗号分隔)
|
||||
TRUSTED_PROXY_IPS=127.0.0.1
|
||||
```
|
||||
|
||||
**常见部署配置**:
|
||||
|
||||
| 部署方式 | TRUSTED_PROXY_COUNT | TRUSTED_PROXY_IPS |
|
||||
|:---------|:----|:----|
|
||||
| 直连(无代理) | `0` | 留空 |
|
||||
| 单层 Nginx | `1` | `127.0.0.1`(本机)或 Nginx 内网 IP |
|
||||
| Nginx + CDN | `2` | Nginx 内网 IP |
|
||||
| Docker bridge 映射端口 | `0` | 留空(容器外为客户端直连) |
|
||||
|
||||
:::warning 安全提示
|
||||
如果应用被直接暴露在公网(如 Docker bridge 映射端口、未走 Nginx),务必保持 `TRUSTED_PROXY_COUNT=0` 或 `TRUSTED_PROXY_IPS` 留空。否则客户端可随意伪造 XFF 首部,绕过基于 IP 的登录锁定和请求限流。
|
||||
:::
|
||||
|
||||
## 二、IP 地理位置查询
|
||||
|
||||
位于 `src/utils/ip2region.py`,基于 [ip2region](https://github.com/lionsoul2014/ip2region) 离线数据库,无需网络请求。
|
||||
@@ -406,17 +384,21 @@ if count and int(count) >= CAPTCHA_RATE_LIMIT:
|
||||
|
||||
## 五、配置参考
|
||||
|
||||
### 完整 .env 配置
|
||||
### Nginx 代理配置
|
||||
|
||||
```nginx
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.0.1:8031/api/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
# 透传上游X-Forwarded-For(不追加本机IP);无上游头时回退到直连IP
|
||||
proxy_set_header X-Forwarded-For $http_x_forwarded_for;
|
||||
}
|
||||
```
|
||||
|
||||
### 请求限流配置(.env)
|
||||
|
||||
```bash
|
||||
# ============================================================
|
||||
# 可信代理配置
|
||||
# ============================================================
|
||||
# 信任的代理层数(0 = 不信任代理头,直接使用 request.client.host)
|
||||
TRUSTED_PROXY_COUNT=1
|
||||
# 可信代理IP白名单(逗号分隔,仅白名单内的直连对端才信任 XFF)
|
||||
TRUSTED_PROXY_IPS=127.0.0.1
|
||||
|
||||
# ============================================================
|
||||
# 请求限流配置
|
||||
# ============================================================
|
||||
@@ -436,7 +418,7 @@ RATE_LIMIT_KEY_PREFIX=rl:
|
||||
|
||||
**原因**:本地直连无代理,`request.client.host` 就是 `127.0.0.1`。
|
||||
|
||||
**说明**:这是正常行为。生产环境配置 `TRUSTED_PROXY_COUNT` 和 `TRUSTED_PROXY_IPS` 后即可获取真实客户端 IP。
|
||||
**说明**:这是正常行为。生产环境部署在 Nginx/CDN 后会从 `X-Forwarded-For` 获取真实客户端 IP。
|
||||
|
||||
### 地理位置显示为"未知"
|
||||
|
||||
@@ -460,7 +442,7 @@ RATE_LIMIT_KEY_PREFIX=rl:
|
||||
|
||||
1. 检查 `.env` 中 `RATE_LIMIT_ENABLED=True`
|
||||
2. 检查 Redis 连接是否正常
|
||||
3. 检查 `TRUSTED_PROXY_COUNT` 配置——如果限流基于 `request.client.host`(代理 IP),所有请求会共享同一 IP,导致限流提前触发或形同虚设
|
||||
3. 检查 Nginx 是否正确透传 `X-Forwarded-For`——如果限流基于代理 IP 而非真实客户端 IP,所有请求会共享同一 IP,导致限流提前触发或形同虚设
|
||||
|
||||
### 如何更新 ip2region 数据库
|
||||
|
||||
@@ -470,7 +452,7 @@ RATE_LIMIT_KEY_PREFIX=rl:
|
||||
|
||||
| 模块 | 文件 | 核心能力 | 降级策略 |
|
||||
|:-----|:-----|:---------|:---------|
|
||||
| IP 获取 | `src/utils/ip.py` | 从 Request 提取真实客户端 IP,支持可信代理白名单 | client 为 None 时回退 `127.0.0.1` |
|
||||
| IP 获取 | `src/utils/ip.py` | 从 Request 提取真实客户端 IP(优先 X-Forwarded-For 最左侧) | client 为 None 时回退 `127.0.0.1` |
|
||||
| 地理位置 | `src/utils/ip2region.py` | 基于 ip2region.xdb 离线查询,零网络依赖 | 异常/畸形/内网统一降级返回 `"未知"` 或 `"内网IP"` |
|
||||
| UA 解析 | `src/utils/ua.py` | 正则匹配提取操作系统和浏览器 | UA 为空时返回 `('', '')` |
|
||||
|
||||
|
||||
Reference in New Issue
Block a user