django 4.1+ 中间件必须继承 basemiddleware 或实现 call 方法以支持异步视图;ip 黑名单需通过 redis 缓存 ip 集合、结合可信 x-forwarded-for 解析真实客户端 ip,并返回 httpresponseforbidden 响应。

中间件类必须继承 BaseMiddleware 或实现 __call__ 方法
Django 4.1+ 推荐用函数式中间件或继承 BaseMiddleware,旧版的类中间件(带 process_request)在异步视图中不兼容。直接写一个继承 BaseMiddleware 的类最稳妥,它天然支持同步/异步请求。
关键点是:不能只依赖 process_request,否则异步视图(如 async def view(...))会跳过拦截。必须让中间件本身可 await。
-
BaseMiddleware子类自动包装为异步就绪,无需额外判断 - 若手动实现
__call__,需检查await路由匹配结果,否则可能在 ASGI 模式下漏判 - 中间件顺序很重要:
IPBlacklistMiddleware应放在SecurityMiddleware之后、视图中间件之前,确保 IP 已被标准化(如处理了代理头)
从哪里读取黑名单?别硬编码,用缓存 + 数据库双源
实时拦截要求黑名单能秒级更新,但每次请求都查数据库太重;全放内存又难热更新。推荐「Redis 缓存为主 + 定时/信号同步数据库」模式。
示例逻辑:中间件中用 cache.get('ip_blacklist_set') 获取已预加载的 IP 集合(set 类型),不是每次 cache.get(f'blacklist:{ip}') 单查——后者 O(1) 但网络往返多,集合一次拉取更省。
- 初始化时用
cache.get_or_set('ip_blacklist_set', load_from_db, timeout=30),避免缓存击穿 - 后台管理端新增黑名单后,触发
cache.delete('ip_blacklist_set'),下个请求自动重载 - 若用 SQLite 或低流量场景,可改用
django.core.cache.caches['default']+LocMemCache,但注意多进程下不共享,得换RedisCache
如何获取真实客户端 IP?别信 request.META['REMOTE_ADDR']
在 Nginx / Cloudflare 后面时,REMOTE_ADDR 是上游服务器地址(如 127.0.0.1),真实 IP 在 X-Forwarded-For 或 CF-Connecting-IP 里。Django 不自动解析,得自己写逻辑。
必须结合 SECURE_PROXY_SSL_HEADER 和 USE_X_FORWARDED_HOST 配置,并验证代理链可信,否则可被伪造。
- 先检查
request.META.get('HTTP_X_FORWARDED_FOR'),取最左非私有 IP(如'203.0.113.5, 192.168.1.1'→ 选203.0.113.5) - 若为空,再看
request.META.get('HTTP_CF_CONNECTING_IP')(Cloudflare) - 最后 fallback 到
request.META['REMOTE_ADDR'],但仅当明确无反向代理时才信任 - 务必校验 IP 格式:
ipaddress.ip_address(ip_str),防止注入或异常字符串导致KeyError
拦截后返回什么响应?用 HttpResponseForbidden 别用 redirect
黑名单拦截是服务端策略决策,不是业务跳转。返回 403 响应体最小、语义准确,且不会暴露内部路径。
不要用 redirect('/blocked/'):一是增加一次 HTTP 跳转(浪费 RTT),二是可能被绕过(比如直接请求原路径);也不要返回空响应或 200 + JSON 错误,前端无法统一识别。
- 直接
return HttpResponseForbidden("IP blocked"),Body 可为空以减少带宽 - 若需日志审计,加一行
logger.warning(f"Blocked IP: {client_ip}"),但别打完整请求头 - 注意:某些 CDN(如 Cloudflare)对 403 响应可能做自定义页面,确认是否要透传给终端用户
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











