61 lines
2.0 KiB
Python
61 lines
2.0 KiB
Python
from fastapi import Request
|
||
|
||
from config.env import AppConfig
|
||
|
||
|
||
class ClientIPUtil:
|
||
"""
|
||
客户端IP提取工具
|
||
"""
|
||
|
||
@classmethod
|
||
def get_client_ip(cls, request: Request) -> str:
|
||
"""
|
||
获取客户端真实IP
|
||
|
||
仅当请求来源命中可信代理列表,且可信代理跳数大于0时,才会解析
|
||
X-Forwarded-For / X-Real-IP 请求头;否则回退到直接连接来源地址。
|
||
|
||
:param request: 当前请求对象
|
||
:return: 客户端IP
|
||
"""
|
||
remote_addr = request.client.host if request.client else 'unknown'
|
||
if AppConfig.app_trusted_proxy_hops <= 0:
|
||
return remote_addr
|
||
if not cls._should_trust_proxy_headers(remote_addr):
|
||
return remote_addr
|
||
|
||
forwarded_for = request.headers.get('X-Forwarded-For', '')
|
||
if forwarded_for:
|
||
forwarded_chain = [item.strip() for item in forwarded_for.split(',') if item.strip()]
|
||
if forwarded_chain:
|
||
if len(forwarded_chain) > AppConfig.app_trusted_proxy_hops:
|
||
return forwarded_chain[-(AppConfig.app_trusted_proxy_hops + 1)]
|
||
return forwarded_chain[0]
|
||
|
||
real_ip = request.headers.get('X-Real-IP', '').strip()
|
||
if real_ip:
|
||
return real_ip
|
||
|
||
return remote_addr
|
||
|
||
@classmethod
|
||
def _should_trust_proxy_headers(cls, remote_addr: str) -> bool:
|
||
"""
|
||
判断当前连接来源是否属于可信代理
|
||
|
||
:param remote_addr: 与应用直接建立连接的来源IP
|
||
:return: 是否信任代理头
|
||
"""
|
||
trusted_proxy_ips = cls._get_trusted_proxy_ips()
|
||
return '*' in trusted_proxy_ips or remote_addr in trusted_proxy_ips
|
||
|
||
@classmethod
|
||
def _get_trusted_proxy_ips(cls) -> set[str]:
|
||
"""
|
||
获取可信代理IP集合
|
||
|
||
:return: 可信代理IP集合
|
||
"""
|
||
return {item.strip() for item in AppConfig.app_trusted_proxy_ips.split(',') if item.strip()}
|