Django如何获取请求IP?从踩坑到最佳实践

最近在项目中遇到一个诡异的问题:使用域名访问能获取到真实IP,但用IP+端口访问时,获取到的却是 127.0.0.1。经过一番折腾,终于搞清楚了原因。今天就来总结一下 Django获取客户端IP的正确姿势


❌ 常见的错误写法

很多同学(包括我自己)一开始会这样写:

python 复制代码
# ❌ 错误:'IP' 不是Django的标准META key
ip = request.META['IP']  # KeyError!

# ❌ 错误:只用REMOTE_ADDR,代理时会出错
ip = request.META['REMOTE_ADDR']  # 代理时返回127.0.0.1

踩坑现场

  • 直连服务器:✅ 能获取到真实IP
  • Nginx反向代理:❌ 返回 127.0.0.1
  • CDN/WAF:❌ 返回CDN的IP,不是用户真实IP

📚 Django的META中有哪些IP相关的Key?

META Key 说明 直连 代理 CDN 优先级
REMOTE_ADDR 直连的客户端IP ✅ 真实IP ❌ 代理IP(127.0.0.1) ❌ 最后一跳IP ⭐⭐
HTTP_X_FORWARDED_FOR 代理链中的真实IP ❌ 无 ✅ 真实IP ✅ 真实IP ⭐⭐⭐⭐
HTTP_X_REAL_IP Nginx传递的真实IP ❌ 无 ✅ 真实IP ⭐⭐⭐
HTTP_CLIENT_IP 备用方案 ❌ 无 ⭐⭐

优先级

复制代码
HTTP_X_FORWARDED_FOR > HTTP_X_REAL_IP > REMOTE_ADDR

🎯 最佳实践:通用获取IP函数

⭐ 方法1:标准写法(推荐)⭐⭐⭐

python 复制代码
def get_client_ip(request):
    """
    获取客户端真实IP(支持直连、代理、CDN)
    
    Args:
        request: Django的HttpRequest对象
    
    Returns:
        str: 客户端真实IP
    """
    # 1. 优先从 HTTP_X_FORWARDED_FOR 获取(代理传递的真实IP)
    x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
    if x_forwarded_for:
        # 可能有多个IP(如:client, proxy1, proxy2),取第一个
        ip = x_forwarded_for.split(',')[0].strip()
    else:
        # 2. 没有代理,直接用 REMOTE_ADDR
        ip = request.META.get('REMOTE_ADDR', '127.0.0.1')
    
    # 3. 过滤内网IP(可选,防止伪造)
    if ip.startswith('127.') or ip.startswith('192.168.'):
        return '0.0.0.0'
    
    return ip

使用示例

python 复制代码
def my_view(request):
    ip = get_client_ip(request)
    print(f"客户端IP: {ip}")  # 输出:123.45.67.89
    return JsonResponse({'ip': ip})

⭐ 方法2:更严谨的写法(防伪造)⭐⭐⭐⭐

python 复制代码
def get_client_ip(request):
    """
    获取客户端真实IP(防伪造、支持多层代理)
    """
    # 尝试多个可能的IP来源(按优先级排序)
    ip_keys = [
        'HTTP_X_FORWARDED_FOR',
        'HTTP_X_REAL_IP',
        'HTTP_CLIENT_IP',
        'REMOTE_ADDR',
    ]
    
    for key in ip_keys:
        ip = request.META.get(key)
        if ip:
            # HTTP_X_FORWARDED_FOR 可能有多个IP,取第一个
            if key == 'HTTP_X_FORWARDED_FOR':
                ip = ip.split(',')[0].strip()
            
            # 过滤内网IP和无效IP
            if ip and not ip.startswith(('127.', '192.168.', '0.')):
                return ip
    
    return '0.0.0.0'

⭐ 方法3:使用中间件(全局生效)⭐⭐⭐⭐⭐

如果你想在所有视图中都能用 request.client_ip,可以写个中间件:

python 复制代码
# middleware.py
class ClientIPMiddleware:
    """
    中间件:自动在request对象上添加client_ip属性
    """
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        # 获取真实IP
        x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
        if x_forwarded_for:
            request.client_ip = x_forwarded_for.split(',')[0].strip()
        else:
            request.client_ip = request.META.get('REMOTE_ADDR', '127.0.0.1')
        
        response = self.get_response(request)
        return response

配置中间件settings.py):

python 复制代码
MIDDLEWARE = [
    # ... 其他中间件
    'myapp.middleware.ClientIPMiddleware',  # ⭐ 添加这行
]

使用

python 复制代码
def my_view(request):
    ip = request.client_ip  # ⭐ 直接用,超方便!
    print(f"客户端IP: {ip}")
    return JsonResponse({'ip': ip})

🔧 Nginx配置(最优雅的方案)⭐⭐⭐⭐⭐

如果你用Nginx做反向代理,在Nginx层传递真实IP是最优解

nginx 复制代码
server {
    listen 80;
    server_name example.com;
    
    location / {
        proxy_pass http://127.0.0.1:8000;
        
        # ⭐⭐⭐ 关键:传递真实IP ⭐⭐⭐
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

然后Django中直接用:

python 复制代码
# ✅ 现在REMOTE_ADDR就是真实IP了
ip = request.META.get('REMOTE_ADDR')

📊 不同场景对比

场景 直连 Nginx代理 CDN/WAF 推荐方法
REMOTE_ADDR ✅ 真实IP ❌ 127.0.0.1 ❌ CDN IP ❌ 不推荐
HTTP_X_FORWARDED_FOR ❌ 无 ✅ 真实IP ✅ 真实IP ⭐⭐⭐⭐
中间件 request.client_ip ⭐⭐⭐⭐⭐
Nginx配置 ⭐⭐⭐⭐⭐

⚠️ 常见坑点

坑1:HTTP_X_FORWARDED_FOR 可以伪造!

python 复制代码
# 恶意请求(伪造IP)
curl -H "X-Forwarded-For: 8.8.8.8" http://your-server.com

解决方案

  • ✅ 只信任自己的Nginx/CDN(配置白名单)
  • ✅ 过滤内网IP(127.*, 192.168.*
  • ✅ 使用 HTTP_X_REAL_IP(Nginx专属,难以伪造)

坑2:多层代理时,HTTP_X_FORWARDED_FOR 有多个IP

复制代码
用户 → 代理1 → 代理2 → 你的服务器
HTTP_X_FORWARDED_FOR = "用户IP, 代理1IP, 代理2IP"

解决方案

python 复制代码
# 取第一个(最左边的是真实IP)
ip = request.META.get('HTTP_X_FORWARDED_FOR').split(',')[0].strip()

坑3:IPv6的IP获取

python 复制代码
# IPv6地址示例:2001:0db8:85a3::8a2e:0370:7334
# 同样适用上述方法
ip = get_client_ip(request)  # 支持IPv4和IPv6

🎁 完整工具类(直接复制用)

python 复制代码
# utils.py
class IPUtils:
    @staticmethod
    def get_client_ip(request):
        """获取客户端真实IP(防伪造、支持代理)"""
        for key in ['HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'HTTP_CLIENT_IP', 'REMOTE_ADDR']:
            ip = request.META.get(key)
            if ip:
                if key == 'HTTP_X_FORWARDED_FOR':
                    ip = ip.split(',')[0].strip()
                if ip and not ip.startswith(('127.', '192.168.', '0.')):
                    return ip
        return '0.0.0.0'

# 使用
from myapp.utils import IPUtils
ip = IPUtils.get_client_ip(request)

📝 总结

方法 代码 推荐度 适用场景
request.META['IP'] 错误 别用!
get_client_ip() 上面的函数 ⭐⭐⭐⭐⭐ 通用推荐
✅ 中间件 request.client_ip ⭐⭐⭐⭐⭐ 全局使用
✅ Nginx配置 proxy_set_header ⭐⭐⭐⭐⭐ 有Nginx时

🎯 最终推荐

python 复制代码
# ✅ 最简单:复制这个函数就够了
def get_client_ip(request):
    x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
    if x_forwarded_for:
        return x_forwarded_for.split(',')[0].strip()
    return request.META.get('REMOTE_ADDR', '127.0.0.1')

# 使用
ip = get_client_ip(request)

一句话总结:别用 request.META['IP'],用 HTTP_X_FORWARDED_FORREMOTE_ADDR 🎉


💡 提示 :如果你的服务在CDN后面(如Cloudflare),记得在CDN控制台开启"传递真实IP"功能,否则 HTTP_X_FORWARDED_FOR 也会是CDN的IP哦!


参考资料


如果这篇博客帮到你了,记得点赞收藏哦!有问题欢迎在评论区交流~ 💬

相关推荐
KaMeidebaby6 分钟前
卡梅德生物技术快报|纯化重组蛋白实操详解
人工智能·python·tcp/ip·算法·机器学习
zjun10011 小时前
TCP专栏-4.四次挥手
网络协议·tcp/ip
酉鬼女又兒3 小时前
零基础入门计算机网络:网络层核心任务、三大关键问题、两种服务类型与 TCP/IP 网际层协议体系全解析
服务器·网络·网络协议·tcp/ip·计算机网络·php·求职招聘
不考研当牛马4 小时前
Django 框架 深度学习
python·深度学习·django
TechWayfarer5 小时前
IP画像在企业安全中的应用:它能做什么?不能替代什么
网络·python·tcp/ip·安全·网络安全
天启HTTP6 小时前
开启全局代理后网络变慢,问题出在哪
开发语言·前端·网络·tcp/ip·php
右耳朵猫AI7 小时前
Python周刊2026W22 | Django 6.1 Alpha 1发布、Nuitka 4.1发布、PEP 831终稿、PEP 808已接受
开发语言·python·django
Wonderful U7 小时前
Python+Django实战|美食菜谱分享与食材采购一体化系统:食谱发布收藏、图文教程、食材商城、购物车、订单管理、美食点评、智能食谱推荐
python·django·美食
刘哥测评技术zcwz6267 小时前
海外动态IP和静态IP需要怎么选择
网络·网络协议·tcp/ip
MIXLLRED7 小时前
随笔——从“IP与网关不在同一网段”说起:网络连接故障的通用排查指南
网络·网络协议·tcp/ip