Wie erhalte ich die Benutzer-IP-Adresse in Django?

287

Wie erhalte ich die IP des Benutzers in Django?

Ich habe eine Ansicht wie diese:

# 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())

Aber ich bekomme diesen Fehler:

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
Benutzerbild
quelle
2
Versuchen Sie, die Anfrage zu entleeren.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', ' , 'SERVER_PORT', 'wsgi.input', 'HTTP_HOST', 'wsgi.multithread', 'HTTP_CACHE_CONTROL', 'HTTP_ACCEPT', 'wsgi.version', 'wsgi.run_once', 'wsgi.errors'. Multiprozess ',' HTTP_ACCEPT_LANGUAGE ',' CONTENT_TYPE ',' CSRF_COOKIE ',' HTTP_ACCEPT_ENCODING ']
Avatar
2
Vielen Dank für diese tolle Frage. Mein Fastcgi hat den Metaschlüssel REMOTE_ADDR nicht übergeben. Ich habe die folgende Zeile in der nginx.conf hinzugefügt und das Problem behoben: fastcgi_param REMOTE_ADDR $ remote_addr;
Avatar

Antworten:

434
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

Stellen Sie sicher, dass Sie den Reverse-Proxy (falls vorhanden) richtig konfiguriert haben (z. B. mod_rpaffür Apache installiert).

Hinweis: In den obigen Abschnitten wird das erste Element in verwendet X-Forwarded-For, Sie möchten jedoch möglicherweise das letzte Element verwenden (z. B. im Fall von Heroku: Erhalten Sie die echte IP-Adresse des Kunden auf Heroku ).

Und dann übergeben Sie einfach die Anfrage als Argument an sie;

get_client_ip(request)
yanchenko
quelle
8
Rufen Sie ip = get_client_ip(request)Ihre Ansichtsfunktion auf.
Yanchenko
4
Die echte Client-IP-Adresse ist nicht die erste, sondern die letzte in HTTP_X_FORWARDED_FOR (siehe Wikipedia-Seite)
Jujule
5
@jujule Das stimmt nicht. Das Format ist normalerweise X-Forwarded-For: client, proxy1, proxy2. Die erste Adresse ist also die des Kunden.
Michael Wasserfall
51
Diese Funktion ist gefährlich. Bei vielen Setups kann ein böswilliger Benutzer leicht dazu führen, dass diese Funktion eine beliebige Adresse zurückgibt (anstelle der tatsächlichen). Siehe esd.io/blog/flask-apps-heroku-real-ip-spoofing.html
Eli
8
Aus den Django-Dokumenten geht hervor, dass "das Verlassen auf REMOTE_ADDR oder ähnliche Werte allgemein als die schlechteste Praxis bekannt ist" ( djangoproject.com/weblog/2009/jul/28/security/#secondary-issue )
Zags,
209

Sie können Django-IPware verwenden , die Python 2 und 3 unterstützt und IPv4 und IPv6 verarbeitet .

Installieren:

pip install django-ipware

Einfache Verwendung:

# 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)

Erweiterte Verwendung:

  • Benutzerdefinierter Header - Benutzerdefinierter Anforderungsheader für IPware:

    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'])
  • Proxy-Anzahl - Der Django-Server steht hinter einer festen Anzahl von Proxys:

    i, r = get_client_ip(request, proxy_count=1)
  • Vertrauenswürdige Proxies - Der Django-Server steht hinter einem oder mehreren bekannten und vertrauenswürdigen Proxys:

    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.'))

Hinweis: Lesen Sie diesen Hinweis .

un33k
quelle
17
Schauen Sie sich den Quellcode an. Es behandelt alle Komplikationen, die durch die anderen Antworten hier identifiziert werden.
HostedMetrics.com
5
Thx @Heliodor - Ja, ich habe das Modul für einen durchschnittlichen Anwendungsfall sehr einfach und für einen komplexen Anwendungsfall sehr flexibel gemacht. Minimal sollten Sie sich die Github-Seite ansehen, bevor Sie Ihre eigene rollen.
Un33k
3
HINWEIS: Die Einstellungen von django-ipware sind standardmäßig nicht sicher! Jeder kann eine der anderen Variablen übergeben und Ihre Site protokolliert diese IP. StellenIPWARE_META_PRECEDENCE_LIST Sie immer die Variable ein, die Sie verwenden, oder verwenden Sie eine Alternative wie pypi.python.org/pypi/WsgiUnproxy
vdboor
@vdboor Könntest du etwas näher darauf eingehen? Ich kann IPWARE_META_PRECEDENCE_LIST im Repo nicht finden.
Monolith
2
@ThaJay Bitte beachten Sie, dass Sie ab 2.0.0 verwenden sollten get_client_ip(). get_real_ipist veraltet und wird in 3.0 entfernt.
Un33k
77

Alexanders Antwort ist großartig, aber es fehlt die Behandlung von Proxys, die manchmal mehrere IPs im HTTP_X_FORWARDED_FOR-Header zurückgeben.

Die tatsächliche IP befindet sich normalerweise am Ende der Liste, wie hier erläutert: http://en.wikipedia.org/wiki/X-Forwarded-For

Die Lösung ist eine einfache Modifikation von Alexanders Code:

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
quelle
5
Ja, die IP steht am Anfang der Liste. Das hier ist falsch.
Pykler
4
Wenn sich der Benutzer hinter einem Proxy befindet, erhalten Sie die interne IP-Adresse des Benutzers, dh eine RFC 1918-Adresse. In den meisten Fällen ist das überhaupt nicht sehr wünschenswert. Diese Lösung konzentriert sich darauf, die externe IP-Adresse des Clients (die Proxy-Adresse) abzurufen, die die am weitesten rechts stehende Adresse ist.
Sævar
2
Danke dir. Wenn ich Schlüssel von anfordere, request.METAgebe ich normalerweise einen Standardwert an, da die Header häufig falsch sind:request.META.get('REMOTE_ADDR', None)
Carl G
2
@CarlG Ihr Code ist transparenter, aber die get-Methode wird von django.utils.datastructures.MultiValueDict geerbt und der Standardwert ist None. Es ist jedoch auf jeden Fall sinnvoll, einen Standardwert anzugeben, wenn Sie tatsächlich möchten, dass er nicht "Keine" ist.
Sævar
2
Sofern Sie X-Forwarded-For nicht bereinigen, wenn Anforderungen Ihren ersten Server erreichen, wird der erste Wert in dieser Liste vom Benutzer angegeben . Ein böswilliger Benutzer kann leicht jede gewünschte IP-Adresse fälschen. Die gewünschte Adresse ist die erste IP vor einem Ihrer Server, nicht unbedingt die erste in der Liste.
Eli
12

Ich möchte eine Verbesserung der Antwort von yanchenko vorschlagen.

Anstatt die erste IP in der X_FORWARDED_FOR-Liste zu verwenden, nehme ich die erste, die in keiner bekannten internen IP enthalten ist, da einige Router das Protokoll nicht respektieren und Sie interne IPs als ersten Wert der Liste sehen können.

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

Ich hoffe, das hilft anderen Googlern, die das gleiche Problem haben.

Doody P.
quelle
Dieser Code überprüft nicht, ob die IP-Adresse von REMOTE_ADDR privat ist, bevor das Feld HTTP_X_FORWARDED_FOR überprüft wird, wie es wahrscheinlich sein sollte (auch '127.0.0.1' oder '127' sollte wahrscheinlich in PRIVATE_IPS_PREFIX zusammen mit IPv6-Äquivalenten enthalten sein.
Rasmus Kaj
1
Technisch gesehen bedeuten diese Präfixe (172, 192) nicht unbedingt private Adressen.
Maniexx
2
Die für private Netzwerke zugewiesenen Adressbereiche sind: 172.16.0.0–172.31.255.255 (16 Netzwerke der Klasse B), 192.168.0.0–192.168.255.255 (1 Netzwerk der Klasse B) und 10.0.0.0–10.255.255.255 (1) "Klasse A" - oder 256 "Klasse B" -Netzwerke).
Zot
is_valid_ip nicht definiert
Prosenjit
7

Hier ist ein kurzer Einzeiler, um dies zu erreichen:

request.META.get('HTTP_X_FORWARDED_FOR', request.META.get('REMOTE_ADDR', '')).split(',')[0].strip()
Masterbase
quelle
3
Wenn beide None zurückgeben, wird eine Fehlermeldung angezeigt.
Gourav Chawla
6

Die einfachste Lösung (falls Sie fastcgi + nignx verwenden) ist das, was itgorilla kommentiert hat:

Vielen Dank für diese tolle Frage. Mein Fastcgi hat den Metaschlüssel REMOTE_ADDR nicht übergeben. Ich habe die folgende Zeile in der nginx.conf hinzugefügt und das Problem behoben: fastcgi_param REMOTE_ADDR $ remote_addr; - Itgorilla

Ps: Ich habe diese Antwort hinzugefügt, um seine Lösung sichtbarer zu machen.

Juande Carrion
quelle
1
Was ist eine vergleichbare Lösung für Nginx (Reverse Proxy) und Gunicorn? proxy_set_header REMOTE_ADDR $remote_addr;Lindert das Problem nicht, wenn es zu nginx.conf hinzugefügt wird.
Hassan Baig
6

Keine Verwirrung mehr In den jüngsten Versionen von Django wird deutlich erwähnt, dass die IP-Adresse des Clients unter verfügbar ist

request.META.get("REMOTE_ADDR")

Weitere Informationen finden Sie in den Django-Dokumenten

Pardhu
quelle
5

In meinem Fall funktioniert keiner der oben genannten Punkte , daher muss ich uwsgi+ djangoQuellcode überprüfen und statische Parameter in Nginx übergeben und sehen, warum / wie und unten ist, was ich gefunden habe.

Env info:
Python-Version: 2.7.5
Django- (1, 6, 6, 'final', 0)
Version: nginx/1.6.0
Nginx- Version: uwsgi:2.0.7

Informationen zur Umgebungseinstellung:
nginx als Reverse-Proxy, der am Port 80 uwsgi als Upstream-Unix-Socket lauscht, wird schließlich auf die Anfrage antworten

Django Konfigurationsinfo:

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";

alle params in der django app bekommen:

X-Forwarded-For :       10.10.10.10
HTTP_X_FORWARDED_FOR :  20.20.20.20

Fazit:

Grundsätzlich müssen Sie in nginx genau den gleichen Feld- / Parameternamen angeben und request.META[field/param]in der Django-App verwenden.

Und jetzt können Sie entscheiden, ob Sie eine Middleware (Interceptor) hinzufügen oder nur HTTP_X_FORWARDED_FORbestimmte Ansichten analysieren möchten.

xxmajia
quelle
2

Der Grund, warum die Funktionalität ursprünglich aus Django entfernt wurde, war, dass dem Header letztendlich nicht vertraut werden kann. Der Grund ist, dass es leicht zu fälschen ist. Die empfohlene Methode zum Konfigurieren eines Nginx-Reverse-Proxys lautet beispielsweise:

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

Wenn Sie das tun:

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

Ihr Nginx in myhost.com sendet weiter:

X-Forwarded-For: 8.8.8.8, 192.168.1.2, 192.168.1.3

Das X-Real-IPwird die IP - Adresse des ersten vorherigen Proxy, wenn Sie die Anweisungen blind folgen.

Wenn es ein Problem ist, zu vertrauen, wer Ihre Benutzer sind, können Sie Folgendes versuchen django-xff: https://pypi.python.org/pypi/django-xff/

Ferrix
quelle
1

In der obigen Antwort fehlte mir auch der Proxy. Ich habe get_ip_address_from_requestvon django_easy_timezones verwendet .

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

Und hier ist Methode get_ip_address_from_request, IPv4 und IPv6 bereit:

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
quelle
0

In django.VERSION (2, 1, 1, 'final', 0) Anforderungshandler

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()

Wenn Sie den obigen Code zweimal aufrufen, erhalten Sie möglicherweise

AttributeError ("'_ io.BytesIO' Objekt hat kein Attribut 'stream'",)

AttributeError ("'LimitedStream' Objekt hat kein Attribut 'raw'")

CS QGB
quelle