用 ipaddress 模块校验 cidr 网段需先解析为 ip_network 和 ip_address 对象再用 in 判断,禁用字符串直接比较;预加载白名单为 frozenset 提升性能,严格处理 ipv4/ipv6 及代理 ip。

怎么用 ipaddress 模块校验 CIDR 网段(别手写正则)
Python 自带的 ipaddress 是唯一靠谱的 IP 处理方案,别用正则匹配 CIDR,也别手动拆点分十进制。它能天然处理 192.168.0.0/16、::1/128、甚至混合 IPv4/IPv6 的判断逻辑。
常见错误是把字符串当 IP 对象直接比较:"192.168.1.5" in "192.168.0.0/16" —— 这永远返回 False,因为这是字符串 in 字符串。
- 必须先用
ipaddress.ip_network()解析网段,用ipaddress.ip_address()解析客户端 IP - 校验用
ip_address in ip_network,不是字符串包含 - 注意
ip_network()默认严格模式:如果传入"192.168.1.5/24"(主机位非 0),会抛ValueError;加参数strict=False可自动归一化 - IPv6 地址从
request.remote_addr拿到时可能带::ffff:前缀(IPv4-mapped),需用addr.ipv4_mapped or addr兼容处理
import ipaddress <p>def is_in_whitelist(ip_str: str, cidr_list: list[str]) -> bool: try: ip = ipaddress.ip_address(ip_str) except ValueError: return False for cidr in cidr_list: try: net = ipaddress.ip_network(cidr, strict=False) if ip in net: return True except ValueError: continue # 跳过非法 CIDR,如 "abc/24" return False </p>
Django 全局中间件里怎么安全取真实客户端 IP
直接读 request.META.get("REMOTE_ADDR") 在 Nginx 反代后基本没用——它返回的是 Nginx 的内网地址(如 127.0.0.1)。必须依赖 X-Forwarded-For,但不能无条件信任。
容易踩的坑:没做代理链剥离,被伪造头绕过白名单;或没处理多级代理导致取错最左 IP。
- 只信任你控制的反向代理(比如 Nginx)添加的
X-Forwarded-For,并在中间件里配置可信代理列表(settings.TRUSTED_PROXIES = ["127.0.0.1", "::1"]) - 用
request.META.get("HTTP_X_FORWARDED_FOR", "")取值,按逗号分割后取第一个非可信 IP(即最“远”的原始客户端 IP) - 务必过滤空值、私有地址(
127.0.0.0/8、10.0.0.0/8等),防止攻击者填X-Forwarded-For: 127.0.0.1, 1.2.3.4绕过 - 如果用的是
django-ipware库,优先用它的get_client_ip(request),它已内置上述逻辑
FastAPI 怎么在依赖项里拦截非白名单 IP(而不是写一堆 if)
FastAPI 的依赖注入机制比写中间件更轻量、更语义清晰。把 IP 校验封装成 Depends,所有需要保护的路由加一个参数就行,不用侵入业务逻辑。
Python 3.14.2是Python编程语言在2025年12月5日发布的稳定版本,属于3.14系列的第二个维护更新。该版本包含了18项修复,重点解决了多进程、数据类及正则表达式等模块的回归问题,并修复了CVE-2025-12084等安全漏洞。此版本标志着自由线程模式(移除GIL)正式获得官方支持,是Python发展的重要里程碑。
关键点在于:依赖函数必须抛出 HTTPException,否则会被静默忽略;且不能在依赖里直接操作 request.state 或修改响应流程。
- 定义依赖函数,接收
request: Request,调用上面的is_in_whitelist() - 校验失败时 raise
HTTPException(status_code=403, detail="IP not allowed") - 路由中声明为依赖:
def api_route(x: int, _: None = Depends(whitelist_check)): - 注意:依赖执行时机在路径操作函数之前,但晚于中间件;若需记录日志,得在依赖里自己 print 或打结构化日志
- 不要在依赖里做耗时操作(如查数据库白名单),白名单应预加载为内存 list/set
from fastapi import Depends, HTTPException, Request <p>async def whitelist_check(request: Request): client_ip = get_real_ip(request) # 复用上面的取 IP 逻辑 if not is_in_whitelist(client_ip, settings.WHITELIST_CIDRS): raise HTTPException(403, "Forbidden: IP not in whitelist") </p>
为什么用 set 存 CIDR 而不是每次 parse(性能差 10 倍以上)
每次请求都调用 ipaddress.ip_network(cidr) 是严重性能浪费。解析 CIDR 是纯 CPU 计算,不涉及 IO,但 Python 的 ip_network 构造开销大,尤其含 IPv6 时。
实测:1000 次 ip_network("10.0.0.0/8") 耗时约 8ms;而预解析后存进 set,每次只需 in 判断,耗时低于 0.01ms。
- 启动时一次性解析全部白名单 CIDR,转成
frozenset[ipaddress.IPv4Network | ipaddress.IPv6Network] - 运行时只做
ip_address in network_obj,这是 C 层实现,极快 - 别用 dict 存(key 是字符串 CIDR),那又退回到字符串匹配,失去网段语义
- 如果白名单动态更新(如后台改配置),用线程安全的
concurrent.futures.ThreadPoolExecutor+ 原子替换变量,避免 reload 时中间状态失效
复杂点在于 IPv4 和 IPv6 网段要分开存或统一用 ipaddress._BaseNetwork 类型判断;最容易被忽略的是没做异常兜底——某个 CIDR 写错(如 192.168.1.0/33),整个白名单初始化失败,服务起不来。必须在预加载阶段捕获并 warn,跳过非法项。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










