Як отримати IP-адресу користувача в django?


287

Як отримати IP-адресу користувача у django?

У мене такий погляд:

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

Але я отримую цю помилку:

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

2
Спробуйте запит на
демпінг.META.keys

2
['HTTP_COOKIE', 'SCRIPT_NAME', 'REQUEST_METHOD', 'PATH_INFO', 'SERVER_PROTOCOL', 'QUERY_STRING', 'CONTENT_LENGTH', 'HTTP_ACCEPT_CHARSET', 'HTTP_USER_AGENTC,' N ',' HTTP ', HTN' N ',' HTTP ',' HTTP ',' NTP ' , 'SERVER_PORT', 'wsgi.input', 'HTTP_HOST', 'wsgi.multithread', 'HTTP_CACHE_CONTROL', 'HTTP_ACCEPT', 'wsgi.version', 'wsgi.run_once', 'wsgi.errors', 'wsgi. мультипроцес ',' HTTP_ACCEPT_LANGUAGE ',' CONTENT_TYPE ',' CSRF_COOKIE ',' HTTP_ACCEPT_ENCODING ']
аватар

2
Дякую за це чудове запитання. Мій fastcgi не пропускав мета-ключ REMOTE_ADDR. Я додав рядок нижче в nginx.conf і усунув проблему: fastcgi_param REMOTE_ADDR $ remote_addr;
аватар

Відповіді:


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

Переконайтеся, що у вас правильно встановлений зворотний проксі (якщо такий є) (наприклад, mod_rpafвстановлений для Apache).

Примітка: вищевказане використовує перший елемент у X-Forwarded-For, але ви, можливо, захочете використовувати останній елемент (наприклад, у випадку Heroku: Отримайте реальну IP-адресу клієнта на Heroku )

А потім просто передайте запит як аргумент до нього;

get_client_ip(request)

8
Телефонуйте ip = get_client_ip(request)у функції перегляду.
Янченко

4
IP-адреса реального клієнта - не перша, але остання в HTTP_X_FORWARDED_FOR (див. Сторінку wikipedia)
липня 1313

5
@jujule Це невірно. Формат зазвичай X-Forwarded-For: client, proxy1, proxy2. Отже, перша адреса - це клієнт.
Водоспад Майкл

51
Ця функція небезпечна. При багатьох налаштуваннях шкідливий користувач може змусити цю функцію повернути будь-яку потрібну адресу (замість реальної). Дивіться esd.io/blog/flask-apps-heroku-real-ip-spoofing.html
Ілій

8
З документів django, "покладаючись на REMOTE_ADDR або подібні значення, як відомо, є найгіршою практикою" ( djangoproject.com/weblog/2009/jul/28/security/#secondary-issue )
Загс

209

Ви можете використовувати django-ipware, яке підтримує Python 2 & 3 та обробляє IPv4 та IPv6 .

Встановити:

pip install django-ipware

Просте використання:

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

Розширене використання:

  • Спеціальний заголовок - спеціальний заголовок запиту на 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'])
  • Кількість проксі-серверів - сервер Django стоїть за встановленою кількістю проксі-серверів:

    i, r = get_client_ip(request, proxy_count=1)
  • Довірені проксі - сервер Django стоїть за одним або кількома відомими та надійними проксі-серверами:

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

Примітка. Прочитайте це повідомлення .


17
Погляньте на його вихідний код. Він вирішує всі ускладнення, виявлені іншими відповідями тут.
HostedMetrics.com

5
Thx @Heliodor - Так, я зробив модуль дуже простим для середнього випадку використання та дуже гнучким для складного випадку використання. Мінімально, ви хочете переглянути свою сторінку github, перш ніж прокрутити власну.
un33k

3
Зверніть увагу, що налаштування django-ipware за замовчуванням не захищені! Будь-хто може передати одну з інших змінних, і ваш сайт зареєструє цю IP-адресу. Завжди встановлюйте IPWARE_META_PRECEDENCE_LISTзмінну, яку ви використовуєте, або використовуйте таку альтернативу, як pypi.python.org/pypi/WsgiUnproxy
vdboor

@vdboor Не могли б ви трохи розробити? Я не можу знайти IPWARE_META_PRECEDENCE_LIST в репо.
Моноліт

2
@ThaJay Зверніть увагу, що з 2.0.0 ви повинні використовувати get_client_ip(). get_real_ipзастаріла і буде видалена в 3.0.
un33k

77

Відповідь Олександра чудова, але не вистачає обробки проксі-серверів, які іноді повертають кілька IP-адрес у заголовку HTTP_X_FORWARDED_FOR.

Справжній IP зазвичай знаходиться в кінці списку, як пояснено тут: http://en.wikipedia.org/wiki/X-Forwarded-For

Рішення - це проста модифікація коду Олександра:

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

5
Так, ip знаходиться на початку списку. Це тут неправильно.
Pykler

4
Насправді, якщо користувач стоїть за проксі, ви отримаєте внутрішню IP-адресу користувача, тобто адресу RFC 1918. У більшості випадків це зовсім не бажано. Це рішення фокусується на отриманні зовнішньої IP-адреси клієнта (проксі-адреси), яка є найбільш правильною адресою.
Sævar

2
Дякую. Зазвичай, коли я request.METAзапитую ключі, я включаю значення за замовчуванням, оскільки заголовки часто відсутні:request.META.get('REMOTE_ADDR', None)
Carl G

2
@CarlG ваш код прозоріший, але метод get передається у спадок від django.utils.datastructures.MultiValueDict, а значення за замовчуванням - None. Але, безумовно, має сенс включити значення за замовчуванням, якщо ви насправді хотіли, щоб воно було чимось іншим, ніж None.
Sævar

2
Якщо ви не чистите X-Forwarded-For, коли запити потрапляють на ваш перший сервер, тоді перше значення в цьому списку надається користувачем . Зловмисний користувач може легко підробити будь-яку IP-адресу, яку він хоче. Потрібна адреса - це перший IP-адрес перед будь-яким із ваших серверів, не обов’язково перший у списку.
Елі

12

Я хотів би запропонувати покращити відповідь Янченка.

Замість того, щоб брати перший ip у списку X_FORWARDED_FOR, я беру перший, який не є відомим внутрішнім ip, оскільки деякі маршрутизатори не поважають протокол, і ви можете бачити внутрішні ips як перше значення списку.

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

Я сподіваюся, що це допоможе співвітчизникам Google, які мають ті ж проблеми.


Цей код не перевіряє, що ip від REMOTE_ADDR є приватним перед перевіркою поля HTTP_X_FORWARDED_FOR, як це, мабуть, повинно (також "127.0.0.1" або "127.", мабуть, має бути у PRIVATE_IPS_PREFIX разом із еквівалентами IPv6.
Rasmus Kaj

1
Технічно ці префікси (172, 192) не обов'язково означають приватні адреси.
maniexx

2
Діапазони адрес, призначені для приватних мереж, складають: 172.16.0.0–172.31.255.255 (16 мереж «класу В»), 192.168.0.0–192.168.255.255 (1 «мережа класу В») та 10.0.0.0–10.255.255.255 (1 Мережі "класу А" або 256 "класу В").
tzot

is_valid_ip не визначений
Prosenjit

7

ось короткий один вкладиш для цього:

request.META.get('HTTP_X_FORWARDED_FOR', request.META.get('REMOTE_ADDR', '')).split(',')[0].strip()

3
Якщо вони обоє не повернуть None, то ви отримаєте помилку.
Gourav Chawla

6

Найпростіше рішення (якщо ви користуєтеся fastcgi + nignx) - це те, що прокоментував itgorilla:

Дякую за це чудове запитання. Мій fastcgi не пропускав мета-ключ REMOTE_ADDR. Я додав рядок нижче в nginx.conf і усунув проблему: fastcgi_param REMOTE_ADDR $ remote_addr; - itgorilla

Пса: Цю відповідь я додав лише для того, щоб зробити його рішення більш помітним.


1
Яке порівнянне рішення для nginx (зворотний проксі) та guicorn? proxy_set_header REMOTE_ADDR $remote_addr;не полегшує проблему при додаванні до nginx.conf.
Хассан Байг

6

Більше плутанини В останніх версіях Django чітко зазначається, що Ip-адреса клієнта доступна за адресою

request.META.get("REMOTE_ADDR")

для отримання додаткової інформації перегляньте Документи Django


5

У моєму випадку жодне з вищезгаданих не працює, тому мені доведеться перевірити uwsgi+ djangoвихідний код і передати статичний парам у nginx і подивитися, чому / як, і нижче - що я знайшов.

Інформація про програму:
версія python : версія 2.7.5
Django: (1, 6, 6, 'final', 0)
версія nginx: nginx/1.6.0
uwsgi:2.0.7

Інформація про налаштування Env:
nginx як прослуховування зворотного проксі-сервера в порту 80 uwsgi як socket unix socket, в кінцевому підсумку відповість на запит

Інформація про конфігурацію Джанго:

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

отримання всіх парам в додатку django:

X-Forwarded-For :       10.10.10.10
HTTP_X_FORWARDED_FOR :  20.20.20.20

Висновок:

Отже, в nginx потрібно вказати точно таку саму назву поля / парами та використовувати request.META[field/param] в додатку django.

Тепер ви можете вирішити, додавати проміжне програмне забезпечення (перехоплювач) або просто розбирати HTTP_X_FORWARDED_FORпевні види.


2

Причина, якою функціональність була видалена з Django спочатку, полягала в тому, що в кінцевому підсумку не можна довіряти заголовку. Причина в тому, що це легко підробляти. Наприклад, рекомендований спосіб налаштування зворотного проксі nginx:

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

Коли ви робите:

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

Ваш nginx на myhost.com надсилатиме далі:

X-Forwarded-For: 8.8.8.8, 192.168.1.2, 192.168.1.3

The X-Real-IPБуде IP першого попереднього проксі - сервера , якщо ви будете слідувати інструкціям наосліп.

Якщо ви довіряєте, хто такі ваші користувачі, це проблема, ви можете спробувати щось на зразок django-xff: https://pypi.python.org/pypi/django-xff/


1

Я також не бракував проксі у вищевказаній відповіді. Я використовував get_ip_address_from_requestз 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

І ось метод get_ip_address_from_request, IPv4 і 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

0

У django.VERSION (2, 1, 1, 'final', 0) обробник запиту

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

якщо ви дзвоните вище коду двічі, ви можете отримати

AttributeError ("" _ io.BytesIO 'об'єкт не має атрибута "stream" ",)

AttributeError (об’єкт "LimitedStream" не має атрибута "raw" ")

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.