¿Cómo obtengo la dirección IP del usuario en django?

287

¿Cómo obtengo la IP del usuario en django?

Tengo una vista como esta:

# Create your views
from django.contrib.gis.utils import GeoIP
from django.template import  RequestContext
from django.shortcuts import render_to_response


def home(request):
  g = GeoIP()
  client_ip = request.META['REMOTE_ADDR']
  lat,long = g.lat_lon(client_ip)
  return render_to_response('home_page_tmp.html',locals())

Pero me sale este error:

KeyError at /mypage/
    'REMOTE_ADDR'
    Request Method: GET
    Request URL:    http://mywebsite.com/mypage/
    Django Version: 1.2.4
    Exception Type: KeyError
    Exception Value:    
    'REMOTE_ADDR'
    Exception Location: /mysite/homepage/views.py in home, line 9
    Python Executable:  /usr/bin/python
    Python Version: 2.6.6
    Python Path:    ['/mysite', '/usr/local/lib/python2.6/dist-packages/flup-1.0.2-py2.6.egg', '/usr/lib/python2.6', '/usr/lib/python2.6/plat-linux2', '/usr/lib/python2.6/lib-tk', '/usr/lib/python2.6/lib-old', '/usr/lib/python2.6/lib-dynload', '/usr/local/lib/python2.6/dist-packages', '/usr/lib/python2.6/dist-packages', '/usr/lib/pymodules/python2.6']
    Server time:    Sun, 2 Jan 2011 20:42:50 -0600
avatar
fuente
2
Intente descargar request.META.keys ()
Martin v. Löwis
2
['HTTP_COOKIE', 'SCRIPT_NAME', 'REQUEST_METHOD', 'PATH_INFO', 'SERVER_PROTOCOL', 'QUERY_STRING', 'CONTENT_LENGTH', 'HTTP_ACCEPT_CHARSET', 'HTTP_USER_AGENT', 'HTTP_Con'. , 'SERVER_PORT', 'wsgi.input', 'HTTP_HOST', 'wsgi.multithread', 'HTTP_CACHE_CONTROL', 'HTTP_ACCEPT', 'wsgi.version', 'wsgi.run_once', 'wsgi.errors', 'wsgi. multiproceso ',' HTTP_ACCEPT_LANGUAGE ',' CONTENT_TYPE ',' CSRF_COOKIE ',' HTTP_ACCEPT_ENCODING ']
avatar
2
Gracias por esta gran pregunta Mi fastcgi no estaba pasando la meta clave REMOTE_ADDR. Agregué la siguiente línea en nginx.conf y solucioné el problema: fastcgi_param REMOTE_ADDR $ remote_addr;
avatar

Respuestas:

435
def get_client_ip(request):
    x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
    if x_forwarded_for:
        ip = x_forwarded_for.split(',')[0]
    else:
        ip = request.META.get('REMOTE_ADDR')
    return ip

Asegúrese de tener el proxy inverso (si lo hay) configurado correctamente (por ejemplo, mod_rpafinstalado para Apache).

Nota: lo anterior utiliza el primer elemento X-Forwarded-For, pero es posible que desee utilizar el último elemento (por ejemplo, en el caso de Heroku: Obtenga la dirección IP real del cliente en Heroku )

Y luego simplemente pasa la solicitud como argumento;

get_client_ip(request)
Yanchenko
fuente
8
Llame ip = get_client_ip(request)en su función de vista.
Yanchenko
44
La dirección IP del cliente real no es la primera sino la última en HTTP_X_FORWARDED_FOR (consulte la página de Wikipedia)
jujule
55
@jujule Eso no es correcto. El formato es típicamente X-Forwarded-For: client, proxy1, proxy2. Entonces, la primera dirección es la del cliente.
Michael Waterfall
51
Esta función es peligrosa. Con muchas configuraciones, un usuario malintencionado podría fácilmente hacer que esta función devuelva cualquier dirección que desee (en lugar de la real). Ver esd.io/blog/flask-apps-heroku-real-ip-spoofing.html
Eli
8
De los documentos de django "confiar en REMOTE_ADDR o valores similares es ampliamente conocido como la peor práctica" ( djangoproject.com/weblog/2009/jul/28/security/#secondary-issue )
Zags
209

Puede usar django-ipware que admite Python 2 y 3 y maneja IPv4 e IPv6 .

Instalar en pc:

pip install django-ipware

Uso simple:

# In a view or a middleware where the `request` object is available

from ipware import get_client_ip
ip, is_routable = get_client_ip(request)
if ip is None:
    # Unable to get the client's IP address
else:
    # We got the client's IP address
    if is_routable:
        # The client's IP address is publicly routable on the Internet
    else:
        # The client's IP address is private

# Order of precedence is (Public, Private, Loopback, None)

Uso avanzado:

  • Encabezado personalizado: encabezado de solicitud personalizado para que ipware lo vea:

    i, r = get_client_ip(request, request_header_order=['X_FORWARDED_FOR'])
    i, r = get_client_ip(request, request_header_order=['X_FORWARDED_FOR', 'REMOTE_ADDR'])
  • Recuento de proxy: el servidor de Django está detrás de un número fijo de servidores proxy:

    i, r = get_client_ip(request, proxy_count=1)
  • Proxies de confianza: el servidor de Django está detrás de uno o más proxies conocidos y de confianza:

    i, r = get_client_ip(request, proxy_trusted_ips=('177.2.2.2'))
    
    # For multiple proxies, simply add them to the list
    i, r = get_client_ip(request, proxy_trusted_ips=('177.2.2.2', '177.3.3.3'))
    
    # For proxies with fixed sub-domain and dynamic IP addresses, use partial pattern
    i, r = get_client_ip(request, proxy_trusted_ips=('177.2.', '177.3.'))

Nota: lea este aviso .

un33k
fuente
17
Echa un vistazo a su código fuente. Maneja todas las complicaciones identificadas por las otras respuestas aquí.
HostedMetrics.com
55
Thx @Heliodor - Sí, he hecho que el módulo sea muy simple para un caso de uso promedio y muy flexible para un caso de uso complejo. Como mínimo, querrás mirar su página de github antes de lanzar la tuya.
un33k
3
¡NOTA que la configuración de django-ipware no es segura por defecto! Cualquiera puede pasar una de las otras variables y su sitio registrará esa IP. Siempre establezca IPWARE_META_PRECEDENCE_LISTla variable que usa, o use una alternativa como pypi.python.org/pypi/WsgiUnproxy
vdboor
@vdboor ¿Podrías elaborar un poco? No puedo encontrar IPWARE_META_PRECEDENCE_LIST en el repositorio.
Monolith
2
@ThaJay Tenga en cuenta que a partir de 2.0.0, debe usar get_client_ip(). get_real_ipestá en desuso y se eliminará en 3.0.
un33k
77

La respuesta de Alexander es excelente, pero carece del manejo de proxies que a veces devuelven múltiples IP en el encabezado HTTP_X_FORWARDED_FOR.

La IP real suele estar al final de la lista, como se explica aquí: http://en.wikipedia.org/wiki/X-Forwards-For

La solución es una simple modificación del código de Alexander:

def get_client_ip(request):
    x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
    if x_forwarded_for:
        ip = x_forwarded_for.split(',')[-1].strip()
    else:
        ip = request.META.get('REMOTE_ADDR')
    return ip
Sævar
fuente
55
Sí, la ip está al principio de la lista. Esto aquí está mal.
Pykler
44
En realidad, si el usuario está detrás de un proxy, obtendrá la dirección IP interna del usuario, es decir, una dirección RFC 1918. En la mayoría de los casos, eso no es muy deseable en absoluto. Esta solución se enfoca en obtener la dirección IP externa del cliente (la dirección proxy), que es la dirección más correcta.
Sævar
2
Gracias. Por lo general, cuando solicito claves request.META, incluyo un valor predeterminado ya que los encabezados a menudo fallan:request.META.get('REMOTE_ADDR', None)
Carl G
2
@CarlG su código es más transparente, pero el método get se hereda de django.utils.datastructures.MultiValueDict y el valor predeterminado es None. Pero definitivamente tiene sentido incluir un valor predeterminado si realmente desea que sea algo distinto de Ninguno.
Sævar
2
A menos que esté fregando X-Forward-For cuando las solicitudes lleguen a su primer servidor, el primer valor en esa lista es proporcionado por el usuario . Un usuario malintencionado podría falsificar fácilmente cualquier dirección IP que desee. La dirección que desea es la primera IP antes de cualquiera de sus servidores, no necesariamente la primera en la lista.
Eli
12

Me gustaría sugerir una mejora a la respuesta de yanchenko.

En lugar de tomar la primera ip en la lista X_FORWARDED_FOR, tomo la primera que no es una ip interna conocida, ya que algunos enrutadores no respetan el protocolo, y puede ver ips internos como el primer valor de la lista.

PRIVATE_IPS_PREFIX = ('10.', '172.', '192.', )

def get_client_ip(request):
    """get the client ip from the request
    """
    remote_address = request.META.get('REMOTE_ADDR')
    # set the default value of the ip to be the REMOTE_ADDR if available
    # else None
    ip = remote_address
    # try to get the first non-proxy ip (not a private ip) from the
    # HTTP_X_FORWARDED_FOR
    x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
    if x_forwarded_for:
        proxies = x_forwarded_for.split(',')
        # remove the private ips from the beginning
        while (len(proxies) > 0 and
                proxies[0].startswith(PRIVATE_IPS_PREFIX)):
            proxies.pop(0)
        # take the first ip which is not a private one (of a proxy)
        if len(proxies) > 0:
            ip = proxies[0]

    return ip

Espero que esto ayude a los compañeros de Google que tienen el mismo problema.

Doody P
fuente
Este código no verifica que la ip de REMOTE_ADDR sea privada antes de verificar el campo HTTP_X_FORWARDED_FOR, como probablemente debería (también, '127.0.0.1' o '127.' probablemente debería estar en PRIVATE_IPS_PREFIX, junto con equivalentes IPv6.
Rasmus Kaj
1
Técnicamente, esos prefijos (172, 192) no necesariamente significan direcciones privadas.
maniexx
2
Los rangos de direcciones asignados para redes privadas son: 172.16.0.0–172.31.255.255 (16 redes “clase B”), 192.168.0.0–192.168.255.255 (1 red “clase B”) y 10.0.0.0–10.255.255.255 (1 "Clase A" o 256 redes "clase B").
tzot
is_valid_ip no definido
Prosenjit
7

Aquí hay un breve resumen para lograr esto:

request.META.get('HTTP_X_FORWARDED_FOR', request.META.get('REMOTE_ADDR', '')).split(',')[0].strip()
base maestra
fuente
3
Si ambos devuelven Ninguno, obtendría un error.
Gourav Chawla
6

La solución más simple (en caso de que esté usando fastcgi + nignx) es lo que itgorilla comentó:

Gracias por esta gran pregunta Mi fastcgi no estaba pasando la meta clave REMOTE_ADDR. Agregué la siguiente línea en nginx.conf y solucioné el problema: fastcgi_param REMOTE_ADDR $ remote_addr; - itgorilla

Ps: agregué esta respuesta solo para hacer que su solución sea más visible.

Juande Carrion
fuente
1
¿Qué es una solución comparable para nginx (proxy inverso) y gunicorn? proxy_set_header REMOTE_ADDR $remote_addr;no alivia el problema cuando se agrega a nginx.conf.
Hassan Baig
6

No más confusión En las versiones recientes de Django se menciona claramente que la dirección IP del cliente está disponible en

request.META.get("REMOTE_ADDR")

Para obtener más información, consulte los documentos de Django

Pardhu
fuente
5

En mi caso, ninguno de los anteriores funciona, así que tengo que verificar uwsgi+ djangocódigo fuente y pasar el parámetro estático en nginx y ver por qué / cómo, y a continuación es lo que he encontrado.

Información de env:
versión de python : versión de 2.7.5
Django: (1, 6, 6, 'final', 0)
versión de nginx: nginx/1.6.0
uwsgi:2.0.7

Información de configuración de env:
nginx como proxy inverso que escucha en el puerto 80 uwsgi como socket de unix ascendente, eventualmente responderá a la solicitud

Información de configuración de Django:

USE_X_FORWARDED_HOST = True # with or without this line does not matter

nginx config:

uwsgi_param      X-Real-IP              $remote_addr;
// uwsgi_param   X-Forwarded-For        $proxy_add_x_forwarded_for;
// uwsgi_param   HTTP_X_FORWARDED_FOR   $proxy_add_x_forwarded_for;

// hardcode for testing
uwsgi_param      X-Forwarded-For        "10.10.10.10";
uwsgi_param      HTTP_X_FORWARDED_FOR   "20.20.20.20";

obteniendo todos los params en la aplicación django:

X-Forwarded-For :       10.10.10.10
HTTP_X_FORWARDED_FOR :  20.20.20.20

Conclusión:

Básicamente, debe especificar exactamente el mismo campo / nombre de parámetro en nginx y usarlo request.META[field/param]en la aplicación django.

Y ahora puede decidir si agregar un middleware (interceptor) o simplemente analizar HTTP_X_FORWARDED_FORen ciertas vistas.

xxmajia
fuente
2

La razón por la que la funcionalidad se eliminó de Django originalmente fue que el encabezado no es confiable. La razón es que es fácil de engañar. Por ejemplo, la forma recomendada de configurar un proxy inverso nginx es:

add_header X-Forwarded-For $proxy_add_x_forwarded_for;
add_header X-Real-Ip       $remote_addr;

Cuando tu lo hagas:

curl -H 'X-Forwarded-For: 8.8.8.8, 192.168.1.2' http://192.168.1.3/

Su nginx en myhost.com enviará en adelante:

X-Forwarded-For: 8.8.8.8, 192.168.1.2, 192.168.1.3

los X-Real-IP será el IP de la primera representación anterior si se siguen las instrucciones ciegamente.

En caso de que confiar en quiénes son sus usuarios sea un problema, puede intentar algo como django-xff: https://pypi.python.org/pypi/django-xff/

ferrix
fuente
1

También me faltaba proxy en la respuesta anterior. Solía get_ip_address_from_requestde django_easy_timezones .

from easy_timezones.utils import get_ip_address_from_request, is_valid_ip, is_local_ip
ip = get_ip_address_from_request(request)
try:
    if is_valid_ip(ip):
        geoip_record = IpRange.objects.by_ip(ip)
except IpRange.DoesNotExist:
    return None

Y aquí está el método get_ip_address_from_request, IPv4 e IPv6 listo:

def get_ip_address_from_request(request):
    """ Makes the best attempt to get the client's real IP or return the loopback """
    PRIVATE_IPS_PREFIX = ('10.', '172.', '192.', '127.')
    ip_address = ''
    x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR', '')
    if x_forwarded_for and ',' not in x_forwarded_for:
        if not x_forwarded_for.startswith(PRIVATE_IPS_PREFIX) and is_valid_ip(x_forwarded_for):
            ip_address = x_forwarded_for.strip()
    else:
        ips = [ip.strip() for ip in x_forwarded_for.split(',')]
        for ip in ips:
            if ip.startswith(PRIVATE_IPS_PREFIX):
                continue
            elif not is_valid_ip(ip):
                continue
            else:
                ip_address = ip
                break
    if not ip_address:
        x_real_ip = request.META.get('HTTP_X_REAL_IP', '')
        if x_real_ip:
            if not x_real_ip.startswith(PRIVATE_IPS_PREFIX) and is_valid_ip(x_real_ip):
                ip_address = x_real_ip.strip()
    if not ip_address:
        remote_addr = request.META.get('REMOTE_ADDR', '')
        if remote_addr:
            if not remote_addr.startswith(PRIVATE_IPS_PREFIX) and is_valid_ip(remote_addr):
                ip_address = remote_addr.strip()
    if not ip_address:
        ip_address = '127.0.0.1'
    return ip_address
Lucas03
fuente
0

En django.VERSION (2, 1, 1, 'final', 0) manejador de solicitudes

sock=request._stream.stream.raw._sock
#<socket.socket fd=1236, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('192.168.1.111', 8000), raddr=('192.168.1.111', 64725)>
client_ip,port=sock.getpeername()

si llama dos veces al código anterior, puede obtener

AttributeError ("'_ io.BytesIO' el objeto no tiene atributo 'stream'",)

AttributeError ("el objeto 'LimitedStream' no tiene el atributo 'raw'")

CS QGB
fuente