更新
This commit is contained in:
@@ -136,10 +136,6 @@ CAPTCHA_NOISE_DOTS=30
|
|||||||
# CORS允许的源 - 逗号分隔
|
# CORS允许的源 - 逗号分隔
|
||||||
CORS_ALLOWED_ORIGINS=http://localhost:8001
|
CORS_ALLOWED_ORIGINS=http://localhost:8001
|
||||||
|
|
||||||
# 可信代理层数 - nginx直接透传CDN的X-Forwarded-For(只传真实客户端IP,不追加)
|
|
||||||
TRUSTED_PROXY_COUNT=1
|
|
||||||
# 可信代理IP白名单 - CDN节点IP不固定,使用0.0.0.0信任所有来源(安全由CDN+nginx保证)
|
|
||||||
TRUSTED_PROXY_IPS=0.0.0.0
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|||||||
@@ -129,10 +129,6 @@ CAPTCHA_NOISE_DOTS=30
|
|||||||
|
|
||||||
# CORS允许的源 - 逗号分隔,如 http://localhost:8001,https://admin.example.com
|
# CORS允许的源 - 逗号分隔,如 http://localhost:8001,https://admin.example.com
|
||||||
CORS_ALLOWED_ORIGINS=http://localhost:8001
|
CORS_ALLOWED_ORIGINS=http://localhost:8001
|
||||||
# 可信代理层数 - 0=不信任X-Forwarded-For;仅部署在Nginx等反代后才设>=1
|
|
||||||
TRUSTED_PROXY_COUNT=1
|
|
||||||
# 可信代理IP白名单(逗号分隔)- nginx同机部署填127.0.0.1;有CDN且动态边缘IP填0.0.0.0
|
|
||||||
TRUSTED_PROXY_IPS=127.0.0.1
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# 邮件配置
|
# 邮件配置
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ from core.config.app import (
|
|||||||
FASTAPI_APP, FASTAPI_ENV, FASTAPI_DEBUG, FASTAPI_DEMO,
|
FASTAPI_APP, FASTAPI_ENV, FASTAPI_DEBUG, FASTAPI_DEMO,
|
||||||
FASTAPI_ROOT_PATH, FASTAPI_TEMPLATE_FOLDER, FASTAPI_STATIC_FOLDER,
|
FASTAPI_ROOT_PATH, FASTAPI_TEMPLATE_FOLDER, FASTAPI_STATIC_FOLDER,
|
||||||
FASTAPI_UPLOAD_DIR, FASTAPI_TEMP_PATH, FASTAPI_FILE_URL,
|
FASTAPI_UPLOAD_DIR, FASTAPI_TEMP_PATH, FASTAPI_FILE_URL,
|
||||||
TRUSTED_PROXY_COUNT, TRUSTED_PROXY_IPS, UPLOAD_ALLOWED_EXTS, UPLOAD_MAX_SIZE_MB,
|
UPLOAD_ALLOWED_EXTS, UPLOAD_MAX_SIZE_MB,
|
||||||
)
|
)
|
||||||
|
|
||||||
# 数据库配置
|
# 数据库配置
|
||||||
@@ -133,7 +133,7 @@ __all__ = [
|
|||||||
'FASTAPI_APP', 'FASTAPI_ENV', 'FASTAPI_DEBUG', 'FASTAPI_DEMO',
|
'FASTAPI_APP', 'FASTAPI_ENV', 'FASTAPI_DEBUG', 'FASTAPI_DEMO',
|
||||||
'FASTAPI_ROOT_PATH', 'FASTAPI_TEMPLATE_FOLDER', 'FASTAPI_STATIC_FOLDER',
|
'FASTAPI_ROOT_PATH', 'FASTAPI_TEMPLATE_FOLDER', 'FASTAPI_STATIC_FOLDER',
|
||||||
'FASTAPI_UPLOAD_DIR', 'FASTAPI_TEMP_PATH', 'FASTAPI_FILE_URL',
|
'FASTAPI_UPLOAD_DIR', 'FASTAPI_TEMP_PATH', 'FASTAPI_FILE_URL',
|
||||||
'TRUSTED_PROXY_COUNT', 'TRUSTED_PROXY_IPS', 'UPLOAD_ALLOWED_EXTS', 'UPLOAD_MAX_SIZE_MB',
|
'UPLOAD_ALLOWED_EXTS', 'UPLOAD_MAX_SIZE_MB',
|
||||||
|
|
||||||
# 数据库配置
|
# 数据库配置
|
||||||
'DB_DRIVER', 'DB_HOST', 'DB_PORT', 'DB_DATABASE', 'DB_USERNAME', 'DB_PASSWORD',
|
'DB_DRIVER', 'DB_HOST', 'DB_PORT', 'DB_DATABASE', 'DB_USERNAME', 'DB_PASSWORD',
|
||||||
|
|||||||
@@ -96,16 +96,6 @@ FASTAPI_FILE_URL = os.getenv('FASTAPI_FILE_URL', '')
|
|||||||
# CORS允许的源(逗号分隔),未配置则允许所有源但不携带凭证
|
# CORS允许的源(逗号分隔),未配置则允许所有源但不携带凭证
|
||||||
CORS_ALLOWED_ORIGINS = os.getenv('CORS_ALLOWED_ORIGINS', '')
|
CORS_ALLOWED_ORIGINS = os.getenv('CORS_ALLOWED_ORIGINS', '')
|
||||||
|
|
||||||
# 可信代理层数(0=不信任X-Forwarded-For,1=一层反向代理,2=两层…)
|
|
||||||
# 仅当应用部署在Nginx等反向代理后才应设置为>=1
|
|
||||||
TRUSTED_PROXY_COUNT = int(os.getenv('TRUSTED_PROXY_COUNT', '0'))
|
|
||||||
# 可信代理IP白名单(逗号分隔,可含CIDR前的字面IP)。仅当直连对端命中此白名单且
|
|
||||||
# TRUSTED_PROXY_COUNT>0 时才信任 X-Forwarded-For;否则 fail-closed 回退 request.client.host。
|
|
||||||
# 防止应用被直接暴露(如 Docker bridge 映射端口)时客户端伪造 XFF 绕过限流/登录锁定
|
|
||||||
TRUSTED_PROXY_IPS = frozenset(
|
|
||||||
ip.strip() for ip in os.getenv('TRUSTED_PROXY_IPS', '').split(',') if ip.strip()
|
|
||||||
)
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# 上传配置
|
# 上传配置
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|||||||
+6
-47
@@ -54,9 +54,6 @@ IP地址工具模块
|
|||||||
(IP地理位置查询已迁移至 utils/ip2region 离线库)
|
(IP地理位置查询已迁移至 utils/ip2region 离线库)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from core.config import TRUSTED_PROXY_COUNT, TRUSTED_PROXY_IPS
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# 获取客户端IP
|
# 获取客户端IP
|
||||||
# ============================================================
|
# ============================================================
|
||||||
@@ -64,11 +61,8 @@ def get_client_ip(request) -> str:
|
|||||||
"""
|
"""
|
||||||
获取客户端真实IP地址
|
获取客户端真实IP地址
|
||||||
|
|
||||||
仅当直连对端命中 TRUSTED_PROXY_IPS 白名单且 TRUSTED_PROXY_COUNT>0 时才信任
|
优先从 X-Forwarded-For 头取最左侧IP(即真实客户端IP),
|
||||||
X-Forwarded-For 头:
|
无代理头时回退到 request.client.host。
|
||||||
- TRUSTED_PROXY_COUNT==0 或 TRUSTED_PROXY_IPS 未配置 / 对端不命中:不信任代理头,
|
|
||||||
直接使用 request.client.host(fail-closed,防止被直接暴露时伪造 XFF)
|
|
||||||
- 命中白名单:从 X-Forwarded-For 右侧取倒数第 N 个 IP 作为真实客户端IP
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
request: FastAPI Request 对象
|
request: FastAPI Request 对象
|
||||||
@@ -76,45 +70,10 @@ def get_client_ip(request) -> str:
|
|||||||
Returns:
|
Returns:
|
||||||
客户端IP地址
|
客户端IP地址
|
||||||
"""
|
"""
|
||||||
# === 临时调试 ===
|
forwarded = request.headers.get('X-Forwarded-For', '')
|
||||||
peer = request.client.host if request.client else 'None'
|
if forwarded:
|
||||||
xff = request.headers.get('X-Forwarded-For', 'EMPTY')
|
# 取最左侧IP(由CDN/反代追加的真实客户端IP,右侧为代理节点IP)
|
||||||
print(f"[DEBUG IP] peer={peer}, trusted={peer in TRUSTED_PROXY_IPS}, count={TRUSTED_PROXY_COUNT}, xff={xff}",
|
return forwarded.split(',')[0].strip()
|
||||||
flush=True)
|
|
||||||
# === 临时调试 ===
|
|
||||||
if TRUSTED_PROXY_COUNT > 0 and _peer_is_trusted_proxy(request):
|
|
||||||
forwarded = request.headers.get('X-Forwarded-For', '')
|
|
||||||
if forwarded:
|
|
||||||
ips = [ip.strip() for ip in forwarded.split(',')]
|
|
||||||
# XFF 最右侧由受信代理追加,客户端可控的伪造条目只可能出现在左侧;
|
|
||||||
# 跳过最右侧 TRUSTED_PROXY_COUNT 个代理条目取真实客户端IP
|
|
||||||
index = len(ips) - TRUSTED_PROXY_COUNT
|
|
||||||
# 代理条目少于预期(如部分代理未追加 XFF)时回退到最右侧,
|
|
||||||
# 避免退回 request.client.host 拿到代理IP、导致反代部署下所有请求共享同一IP
|
|
||||||
if index < 0:
|
|
||||||
index = len(ips) - 1
|
|
||||||
return ips[index]
|
|
||||||
|
|
||||||
client = request.client
|
client = request.client
|
||||||
return client.host if client else '127.0.0.1'
|
return client.host if client else '127.0.0.1'
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# 私有辅助函数
|
|
||||||
# ============================================================
|
|
||||||
def _peer_is_trusted_proxy(request) -> bool:
|
|
||||||
"""直连对端(TCP 层)必须是配置的可信代理IP,才允许信任 X-Forwarded-For
|
|
||||||
|
|
||||||
若 TRUSTED_PROXY_IPS 为空或对端不命中 → 不信任代理头。否则应用被直接暴露
|
|
||||||
(如 Docker bridge 映射端口、未走 Nginx)时,客户端可随意伪造 XFF 首部,
|
|
||||||
绕过基于 IP 的登录锁定/请求限流。
|
|
||||||
|
|
||||||
特殊值: TRUSTED_PROXY_IPS 包含 '0.0.0.0' 时信任所有对端(适用于 CDN 等
|
|
||||||
动态边缘IP场景,安全由 CDN + nginx 保证)。
|
|
||||||
"""
|
|
||||||
if not TRUSTED_PROXY_IPS:
|
|
||||||
return False
|
|
||||||
if '0.0.0.0' in TRUSTED_PROXY_IPS:
|
|
||||||
return True
|
|
||||||
client = request.client
|
|
||||||
return client is not None and client.host in TRUSTED_PROXY_IPS
|
|
||||||
|
|||||||
Reference in New Issue
Block a user