Comment obtenir l'adresse IP de l'utilisateur dans Django?

287

Comment obtenir l'IP de l'utilisateur dans Django?

J'ai une vue comme celle-ci:

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

Mais je reçois cette erreur:

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
la source
2
Essayez de vider la demande.META.keys ()
Martin c.Löwis
2
['HTTP_COOKIE', 'SCRIPT_NAME', 'REQUEST_METHOD', 'PATH_INFO', 'SERVER_PROTOCOL', 'QUERY_STRING', 'CONTENT_LENGTH', 'HTTP_ACCEPT_CHARSET', 'HTTP_USER_AGENT', 'HTTP_CONNECTION_gi', 'HTTP' , '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 ']
avatar
2
Merci pour cette excellente question. Mon fastcgi ne transmettait pas la méta-clé REMOTE_ADDR. J'ai ajouté la ligne ci-dessous dans le nginx.conf et corrigé le problème: fastcgi_param REMOTE_ADDR $ remote_addr;
avatar

Réponses:

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

Assurez-vous que le proxy inverse (le cas échéant) est correctement configuré (par exemple, mod_rpafinstallé pour Apache).

Remarque: ce qui précède utilise le premier élément dans X-Forwarded-For, mais vous voudrez peut-être utiliser le dernier élément (par exemple, dans le cas de Heroku: obtenir la véritable adresse IP du client sur Heroku )

Et puis passez-lui la requête comme argument;

get_client_ip(request)
yanchenko
la source
8
Appelez ip = get_client_ip(request)dans votre fonction d'affichage.
yanchenko
4
La véritable adresse IP du client n'est pas la première mais la dernière dans HTTP_X_FORWARDED_FOR (voir la page wikipedia)
jujule
5
@jujule Ce n'est pas correct. Le format est généralement X-Forwarded-For: client, proxy1, proxy2. La première adresse est donc celle du client.
Michael Waterfall
51
Cette fonction est dangereuse. Avec de nombreuses configurations, un utilisateur malveillant pourrait facilement faire en sorte que cette fonction renvoie n'importe quelle adresse de son choix (au lieu de la vraie). Voir esd.io/blog/flask-apps-heroku-real-ip-spoofing.html
Eli
8
D'après les django docs "s'appuyer sur REMOTE_ADDR ou des valeurs similaires est largement connu pour être une pire pratique" ( djangoproject.com/weblog/2009/jul/28/security/#secondary-issue )
Zags
209

Vous pouvez utiliser django-ipware qui prend en charge Python 2 et 3 et gère IPv4 et IPv6 .

Installer:

pip install django-ipware

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

Utilisation avancée:

  • En-tête personnalisé - En-tête de demande personnalisé pour ipware à regarder:

    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'])
  • Nombre de proxy - Le serveur Django est derrière un nombre fixe de proxys:

    i, r = get_client_ip(request, proxy_count=1)
  • Trusted Proxies - Le serveur Django est derrière un ou plusieurs proxy connus et approuvés:

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

Remarque: lisez cet avis .

un33k
la source
17
Jetez un oeil à son code source. Il gère toutes les complications identifiées par les autres réponses ici.
HostedMetrics.com
5
Thx @Heliodor - Oui, j'ai rendu le module très simple pour un cas d'utilisation moyen et très flexible pour un cas d'utilisation complexe. Au minimum, vous voudriez regarder sa page github avant de lancer la vôtre.
un33k
3
NOTEZ que les paramètres de django-ipware ne sont pas sécurisés par défaut! N'importe qui peut transmettre l'une des autres variables et votre site enregistrera cette IP. Définissez toujours IPWARE_META_PRECEDENCE_LISTla variable que vous utilisez, ou utilisez une alternative comme pypi.python.org/pypi/WsgiUnproxy
vdboor
@vdboor Pourriez-vous élaborer un peu? Je ne trouve pas IPWARE_META_PRECEDENCE_LIST dans le dépôt.
Monolith
2
@ThaJay Veuillez noter qu'à partir de 2.0.0, vous devez utiliser get_client_ip(). get_real_ipest obsolète et sera supprimé dans la version 3.0.
un33k
77

La réponse d'Alexandre est excellente, mais il lui manque la gestion des proxys qui renvoient parfois plusieurs IP dans l'en-tête HTTP_X_FORWARDED_FOR.

La véritable IP est généralement à la fin de la liste, comme expliqué ici: http://en.wikipedia.org/wiki/X-Forwarded-For

La solution est une simple modification du code d'Alexandre:

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
la source
5
Oui, l'ip est au début de la liste. C'est mal ici.
Pykler
4
En fait, si l'utilisateur est derrière un proxy, vous obtiendrez l'adresse IP interne de l'utilisateur, c'est-à-dire une adresse RFC 1918. Dans la plupart des cas, ce n'est pas très souhaitable du tout. Cette solution se concentre sur l'obtention de l'adresse IP externe du client (l'adresse proxy), qui est l'adresse la plus à droite.
Sævar
2
Je vous remercie. Habituellement, lorsque je demande des clés à, request.METAj'inclus une valeur par défaut, car les en-têtes sont souvent en erreur:request.META.get('REMOTE_ADDR', None)
Carl G
2
@CarlG votre code est plus transparent, mais la méthode get est héritée de django.utils.datastructures.MultiValueDict et la valeur par défaut est None. Mais il est certainement judicieux d'inclure une valeur par défaut si vous vouliez que ce soit autre chose que None.
Sævar
2
Sauf si vous nettoyez X-Forwarded-For lorsque des demandes atteignent votre premier serveur, la première valeur de cette liste est fournie par l'utilisateur . Un utilisateur malveillant pourrait facilement usurper n'importe quelle adresse IP qu'il souhaite. L'adresse que vous voulez est la première IP avant l'un de vos serveurs, pas nécessairement la première de la liste.
Eli
12

Je voudrais suggérer une amélioration de la réponse de Yanchenko.

Au lieu de prendre la première IP de la liste X_FORWARDED_FOR, je prends la première qui, dans une IP interne non connue, car certains routeurs ne respectent pas le protocole, et vous pouvez voir les IPS internes comme la première valeur de la liste.

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

J'espère que cela aide les autres Googleurs qui ont le même problème.

Doody P
la source
Ce code ne vérifie pas que l'ip de REMOTE_ADDR est privée avant de vérifier le champ HTTP_X_FORWARDED_FOR, comme il le devrait probablement (également, '127.0.0.1' ou '127.' devrait probablement être dans PRIVATE_IPS_PREFIX, avec les équivalents IPv6.
Rasmus Kaj
1
Techniquement, ces préfixes (172, 192) ne signifient pas nécessairement des adresses privées.
maniexx
2
Les plages d'adresses attribuées pour les réseaux privés sont les suivantes: 172.16.0.0-172.31.255.255 (16 réseaux «classe B»), 192.168.0.0-192.168.255.255 (1 réseau «classe B») et 10.0.0.0-1010.255.255.255 (1 Réseaux "classe A" ou 256 "classe B").
tzot
is_valid_ip non défini
Prosenjit
7

voici une courte ligne pour y parvenir:

request.META.get('HTTP_X_FORWARDED_FOR', request.META.get('REMOTE_ADDR', '')).split(',')[0].strip()
masterbase
la source
3
Si les deux renvoient None, vous obtiendrez une erreur.
Gourav Chawla
6

La solution la plus simple (au cas où vous utilisez fastcgi + nignx) est ce qu'itgorilla a commenté:

Merci pour cette excellente question. Mon fastcgi ne transmettait pas la méta-clé REMOTE_ADDR. J'ai ajouté la ligne ci-dessous dans le nginx.conf et corrigé le problème: fastcgi_param REMOTE_ADDR $ remote_addr; - itgorilla

Ps: J'ai ajouté cette réponse juste pour rendre sa solution plus visible.

Juande Carrion
la source
1
Qu'est-ce qu'une solution comparable pour nginx (reverse proxy) et gunicorn? proxy_set_header REMOTE_ADDR $remote_addr;ne résout pas le problème lorsqu'il est ajouté à nginx.conf.
Hassan Baig
6

No More confusion Dans les versions récentes de Django, il est clairement mentionné que l'adresse IP du client est disponible à l'adresse

request.META.get("REMOTE_ADDR")

pour plus d'informations, consultez les documents Django

Pardhu
la source
5

Dans mon cas, rien de ce qui précède ne fonctionne, donc je dois vérifier uwsgi+ djangole code source et passer un paramètre statique dans nginx et voir pourquoi / comment, et ci-dessous ce que j'ai trouvé.

Info env:
version python : version 2.7.5
Django: (1, 6, 6, 'final', 0)
version nginx: nginx/1.6.0
uwsgi:2.0.7

Env setting info:
nginx en tant que proxy inverse écoutant au port 80 uwsgi en tant que socket unix en amont, répondra éventuellement à la demande

Informations de configuration de Django:

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

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

obtenir tous les paramètres dans l'application django:

X-Forwarded-For :       10.10.10.10
HTTP_X_FORWARDED_FOR :  20.20.20.20

Conclusion:

Donc, fondamentalement, vous devez spécifier exactement le même nom de champ / paramètre dans nginx, et utiliser request.META[field/param] dans l'application django.

Et maintenant, vous pouvez décider d'ajouter un middleware (intercepteur) ou simplement d'analyser HTTP_X_FORWARDED_FORcertaines vues.

xxmajia
la source
2

La raison pour laquelle la fonctionnalité a été supprimée de Django à l'origine était que l'en-tête ne pouvait pas être approuvé en fin de compte. La raison en est qu'il est facile d'usurper. Par exemple, la façon recommandée de configurer un proxy inverse nginx est de:

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

Quand vous faites:

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

Votre nginx sur myhost.com enverra à partir de maintenant:

X-Forwarded-For: 8.8.8.8, 192.168.1.2, 192.168.1.3

le X-Real-IP sera l'IP du premier proxy précédent si vous suivez les instructions à l'aveugle.

Dans le cas où la confiance en vos utilisateurs est un problème, vous pouvez essayer quelque chose comme django-xff: https://pypi.python.org/pypi/django-xff/

ferrix
la source
1

Il me manquait également un proxy dans la réponse ci-dessus. J'ai utilisé 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

Et voici la méthode get_ip_address_from_request, IPv4 et IPv6 prêt:

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
la source
0

Dans django.VERSION (2, 1, 1, 'final', 0) gestionnaire de demande

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 vous appelez le code ci-dessus deux fois,

AttributeError ("'_ io.BytesIO' objet n'a pas d'attribut 'stream'",)

AttributeError ("L'objet 'LimitedStream' n'a pas d'attribut 'raw'"))

CS QGB
la source