Jak uzyskać adres IP użytkownika w Django?

287

Jak uzyskać adres IP użytkownika w django?

Mam taki widok:

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

Ale pojawia się ten błąd:

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
awatara
źródło
2
Spróbuj
zrzucić
2
[„HTTP_COOKIE”, „SCRIPT_NAME”, „REQUEST_METHOD”, „PATH_INFO”, „SERVER_PROTOCOL”, „QUERY_STRING”, „CONTENT_LENGTH”, „HTTP_ACCEPT_CHARSET”, „HTTP_USER_AGENT”, „HTTP_CONVER. , „SERVER_PORT”, „wsgi.input”, „HTTP_HOST”, „wsgi.multithread”, „HTTP_CACHE_CONTROL”, „HTTP_ACCEPT”, „wsgi.version”, „wsgi.run_once”, „wsgi.errors”, „wsgi. multiprocess ”,„ HTTP_ACCEPT_LANGUAGE ”,„ CONTENT_TYPE ”,„ CSRF_COOKIE ”,„ HTTP_ACCEPT_ENCODING ”]
awatar
2
Dziękuję za to świetne pytanie. Mój fastcgi nie przekazywał meta klucza REMOTE_ADDR. Dodałem wiersz poniżej w pliku nginx.conf i naprawiłem problem: fastcgi_param REMOTE_ADDR $ remote_addr;
awatar

Odpowiedzi:

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

Upewnij się, że masz odwrotne proxy (jeśli istnieje) poprawnie skonfigurowane (np. mod_rpafZainstalowane dla Apache).

Uwaga: powyższe wykorzystuje pierwszy element w X-Forwarded-For, ale możesz chcieć użyć ostatniego elementu (np. W przypadku Heroku: Uzyskaj prawdziwy adres IP klienta na Heroku )

A następnie po prostu przekaż żądanie jako argument;

get_client_ip(request)
Janchenko
źródło
8
Wywołaj ip = get_client_ip(request)funkcję widoku.
yanchenko
4
Rzeczywisty adres IP klienta nie jest pierwszym, ale ostatnim w HTTP_X_FORWARDED_FOR (patrz strona wikipedia)
jujule
5
@jujule To nie jest poprawne. Format jest zwykle X-Forwarded-For: client, proxy1, proxy2. Tak więc pierwszy adres należy do klienta.
Michael Waterfall
51
Ta funkcja jest niebezpieczna. Przy wielu konfiguracjach złośliwy użytkownik może łatwo spowodować, że funkcja zwróci dowolny adres (zamiast prawdziwego). Zobacz esd.io/blog/flask-apps-heroku-real-ip-spoofing.html
Eli
8
Z dokumentów django „poleganie na REMOTE_ADDR lub podobnych wartościach jest powszechnie znane jako najgorsza praktyka” ( djangoproject.com/weblog/2009/jul/28/security/#secondary-issue )
Zags
209

Możesz użyć django-ipware, które obsługuje Python 2 i 3 i obsługuje IPv4 i IPv6 .

Zainstalować:

pip install django-ipware

Proste użycie:

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

Zaawansowane użycie:

  • Niestandardowy nagłówek - niestandardowy nagłówek żądania dla ipware do obejrzenia:

    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'])
  • Liczba serwerów proxy - serwer Django stoi za stałą liczbą serwerów proxy:

    i, r = get_client_ip(request, proxy_count=1)
  • Zaufane proxy - serwer Django stoi za co najmniej jednym znanym i zaufanym proxy:

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

Uwaga: przeczytaj to powiadomienie .

un33k
źródło
17
Spójrz na jego kod źródłowy. Zajmuje się wszystkimi komplikacjami zidentyfikowanymi przez inne odpowiedzi tutaj.
HostedMetrics.com
5
Dzięki @Heliodor - Tak, uczyniłem moduł bardzo prostym dla przeciętnego przypadku użycia i bardzo elastycznym dla złożonego przypadku użycia. Minimalnie chciałbyś spojrzeć na jego stronę github, zanim utworzysz własną.
un33k
3
UWAGA: ustawienia django-ipware nie są domyślnie bezpieczne! Każdy może przekazać jedną z pozostałych zmiennych, a witryna zarejestruje ten adres IP. Zawsze ustaw IPWARE_META_PRECEDENCE_LISTzmienną, której używasz, lub użyj alternatywy, takiej jak pypi.python.org/pypi/WsgiUnproxy
vdboor
@vdboor Czy mógłbyś trochę rozwinąć? Nie mogę znaleźć IPWARE_META_PRECEDENCE_LIST w repozytorium.
Monolith,
2
@ThaJay Należy pamiętać, że od wersji 2.0.0 należy używać get_client_ip(). get_real_ipjest przestarzałe i zostanie usunięty w wersji 3.0.
un33k
77

Odpowiedź Aleksandra jest świetna, ale brakuje obsługi serwerów proxy, które czasami zwracają wiele adresów IP w nagłówku HTTP_X_FORWARDED_FOR.

Rzeczywiste IP znajduje się zwykle na końcu listy, jak wyjaśniono tutaj: http://en.wikipedia.org/wiki/X-Forwarded-For

Rozwiązaniem jest prosta modyfikacja kodu Aleksandra:

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
źródło
5
Tak, ip jest na początku listy. To jest źle.
Pykler
4
W rzeczywistości, jeśli użytkownik stoi za serwerem proxy, uzyskasz wewnętrzny adres IP użytkownika, tj. Adres RFC 1918. W większości przypadków nie jest to wcale pożądane. To rozwiązanie koncentruje się na uzyskaniu zewnętrznego adresu IP klienta (adresu proxy), który jest adresem najbardziej prawostronnym.
Sævar
2
Dziękuję Ci. Zwykle, gdy żądam kluczy od request.META, dołączam wartość domyślną, ponieważ nagłówki często się mylą:request.META.get('REMOTE_ADDR', None)
Carl G
2
@CarlG kod jest bardziej przejrzysty, ale metoda get jest dziedziczona z django.utils.datastructures.MultiValueDict, a wartością domyślną jest None. Ale zdecydowanie warto podać wartość domyślną, jeśli naprawdę chcesz, aby była to inna wartość niż Brak.
Sævar,
2
O ile nie przeszukujesz X-Forwarded-For, gdy żądania trafią na twój pierwszy serwer, pierwsza wartość na tej liście jest podana przez użytkownika . Złośliwy użytkownik może łatwo sfałszować dowolny adres IP, który chce. Wymagany adres to pierwszy adres IP przed dowolnym serwerem, niekoniecznie pierwszy na liście.
Eli
12

Chciałbym zasugerować poprawę odpowiedzi Janczenki.

Zamiast wziąć pierwszy adres IP z listy X_FORWARDED_FOR, biorę pierwszy, który nie jest znanym wewnętrznym adresem ip, ponieważ niektóre routery nie przestrzegają protokołu, i możesz zobaczyć wewnętrzny adres IP jako pierwszą wartość listy.

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

Mam nadzieję, że pomoże to innym pracownikom Google, którzy mają ten sam problem.

Doody P.
źródło
Ten kod nie sprawdza, czy adres IP z REMOTE_ADDR jest prywatny przed sprawdzeniem pola HTTP_X_FORWARDED_FOR, jak to prawdopodobnie powinno (także „127.0.0.1” lub „127.” prawdopodobnie powinno być w PRIVATE_IPS_PREFIX, wraz z odpowiednikami IPv6.
Rasmus Kaj
1
Technicznie te prefiksy (172, 192) niekoniecznie oznaczają adresy prywatne.
maniexx
2
Zakresy adresów przypisane do sieci prywatnych to: 172.16.0.0–172.31.255.255 (16 sieci „klasy B”), 192.168.0.0–192.168.255.255 (1 sieć „klasy B”) i 10.0.0.0–10.255.255.255 (1 Sieci „klasy A” lub 256 „klasy B”).
tzot
is_valid_ip nie zdefiniowano
Prosenjit
7

Oto krótki linijka, aby to osiągnąć:

request.META.get('HTTP_X_FORWARDED_FOR', request.META.get('REMOTE_ADDR', '')).split(',')[0].strip()
baza master
źródło
3
Jeśli oba zwrócą Brak, otrzymasz błąd.
Gourav Chawla
6

Najprostszym rozwiązaniem (w przypadku korzystania z fastcgi + nignx) jest to, co komentowało itgorilla:

Dziękuję za to świetne pytanie. Mój fastcgi nie przekazywał meta klucza REMOTE_ADDR. Dodałem wiersz poniżej w pliku nginx.conf i naprawiłem problem: fastcgi_param REMOTE_ADDR $ remote_addr; - itgorilla

Ps: Dodałem tę odpowiedź, aby jego rozwiązanie było bardziej widoczne.

Juande Carrion
źródło
1
Jakie jest porównywalne rozwiązanie dla nginx (odwrotnego proxy) i gunicorn? proxy_set_header REMOTE_ADDR $remote_addr;nie łagodzi problemu po dodaniu do nginx.conf.
Hassan Baig,
6

Nigdy więcej zamieszania W najnowszych wersjach Django wyraźnie wspomniano, że adres IP klienta jest dostępny pod adresem

request.META.get("REMOTE_ADDR")

Aby uzyskać więcej informacji, sprawdź Dokumenty Django

Pardhu
źródło
5

W moim przypadku żadne z powyższych nie działa, więc muszę sprawdzić uwsgi+ djangokod źródłowy i przekazać statyczny parametr w nginx i zobaczyć dlaczego / jak, a poniżej znajduje się to, co znalazłem.

Informacje o środowisku:
wersja python : wersja 2.7.5
Django: (1, 6, 6, 'final', 0)
wersja nginx: nginx/1.6.0
uwsgi:2.0.7

Informacje o ustawieniach env:
nginx jako nasłuch odwrotnego proxy w porcie 80 uwsgi jako gniazdo upstream unix, w końcu odpowie na żądanie

Informacje o konfiguracji Django:

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

Konfiguracja nginx:

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

pobieranie wszystkich parametrów w aplikacji django:

X-Forwarded-For :       10.10.10.10
HTTP_X_FORWARDED_FOR :  20.20.20.20

Wniosek:

Zasadniczo musisz podać dokładnie tę samą nazwę pola / parametru w nginx i użyć request.META[field/param] w aplikacji django.

Teraz możesz zdecydować, czy dodać oprogramowanie pośrednie (przechwytywacz), czy po prostu przeanalizować HTTP_X_FORWARDED_FORniektóre widoki.

xxmajia
źródło
2

Powodem, dla którego funkcja została pierwotnie usunięta z Django, był fakt, że nagłówkowi nie można ostatecznie zaufać. Powodem jest to, że łatwo jest sfałszować. Na przykład zalecanym sposobem skonfigurowania zwrotnego serwera proxy nginx jest:

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

Kiedy to zrobisz:

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

Twój nginx w myhost.com wyśle ​​dalej:

X-Forwarded-For: 8.8.8.8, 192.168.1.2, 192.168.1.3

The X-Real-IPBędzie IP pierwszego poprzedniego pełnomocnika, jeśli postępuj zgodnie z instrukcjami na ślepo.

W przypadku problemów z zaufaniem do twoich użytkowników możesz spróbować czegoś takiego django-xff: https://pypi.python.org/pypi/django-xff/

ferrix
źródło
1

Brakowało mi również proxy w powyższej odpowiedzi. Użyłem get_ip_address_from_requestz 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

A oto metoda get_ip_address_from_requestzgodna z IPv4 i IPv6:

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
źródło
0

W django.VERSION (2, 1, 1, „final”, 0) moduł obsługi żądań

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

jeśli zadzwonisz powyżej powyższego kodu dwa razy, możesz

AttributeError („Obiekt _” io.BytesIO ”nie ma atrybutu„ stream ””,)

AttributeError (obiekt „LimitedStream” nie ma atrybutu „raw” ”)

CS QGB
źródło