Bagaimana cara mendapatkan alamat IP pengguna di Django?

287

Bagaimana cara saya mendapatkan IP pengguna di Django?

Saya memiliki pandangan seperti ini:

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

Tapi saya mendapatkan kesalahan ini:

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
sumber
2
Coba dumping 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_AGENTER,' HTTP '. , 'SERVER_PORT', 'wsgi.input', 'HTTP_HOST', 'wsgi.multithread', 'HTTP_CACHE_CONTROL', 'HTTP_ACCEPT', 'wsgi.version', 'wsgi.run_once', 'wsgi.errors', 'wsgi.errors', 'wsgi. multiproses ',' HTTP_ACCEPT Westph ',' CONTENT_TYPE ',' CSRF_COOKIE ',' HTTP_ACCEPT_ENCODING ']
avatar
2
Terima kasih atas pertanyaan bagus ini. Fastcgi saya tidak melewati kunci meta REMOTE_ADDR. Saya menambahkan baris di bawah ini di nginx.conf dan memperbaiki masalahnya: fastcgi_param REMOTE_ADDR $ remote_addr;
avatar

Jawaban:

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

Pastikan Anda memiliki proxy terbalik (jika ada) yang dikonfigurasi dengan benar (mis. mod_rpafDiinstal untuk Apache).

Catatan: di atas menggunakan item pertamaX-Forwarded-For , tetapi Anda mungkin ingin menggunakan item terakhir (misalnya, dalam kasus Heroku: Dapatkan alamat IP asli klien di Heroku )

Dan kemudian hanya meneruskan permintaan sebagai argumen padanya;

get_client_ip(request)
yanchenko
sumber
8
Panggil ip = get_client_ip(request)fungsi tampilan Anda.
yanchenko
4
Alamat IP klien sebenarnya bukan yang pertama tetapi yang terakhir di HTTP_X_FORWARDED_FOR (lihat halaman wikipedia)
jujule
5
@ jujule Itu tidak benar. Formatnya biasanya X-Forwarded-For: client, proxy1, proxy2. Jadi alamat pertama adalah milik klien.
Air Terjun Michael
51
Fungsi ini berbahaya. Dengan banyak pengaturan, pengguna jahat dapat dengan mudah menyebabkan fungsi ini mengembalikan alamat yang mereka inginkan (bukan alamat asli). Lihat esd.io/blog/flask-apps-heroku-real-ip-spoofing.html
Eli
8
Dari dokumen Django "mengandalkan REMOTE_ADDR atau nilai-nilai serupa secara luas dikenal sebagai praktik terburuk" ( djangoproject.com/weblog/2009/jul/28/security/#secondary-issue )
Zags
209

Anda dapat menggunakan django-ipware yang mendukung Python 2 & 3 dan menangani IPv4 & IPv6 .

Install:

pip install django-ipware

Penggunaan Sederhana:

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

Penggunaan Lanjutan:

  • Header Kustom - Header permintaan kustom untuk ipware untuk dilihat:

    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 Count - server Django ada di belakang sejumlah proxy:

    i, r = get_client_ip(request, proxy_count=1)
  • Proxy Tepercaya - Server Django ada di belakang satu atau lebih proksi yang diketahui & tepercaya:

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

Catatan: baca pemberitahuan ini .

un33k
sumber
17
Lihatlah kode sumbernya. Ini menangani semua komplikasi yang diidentifikasi oleh jawaban lain di sini.
HostedMetrics.com
5
Thx @Heliodor - Yap, saya telah membuat modul sangat sederhana untuk kasus penggunaan rata-rata dan sangat fleksibel untuk kasus penggunaan yang kompleks. Minimal, Anda ingin melihat halaman github-nya sebelum menggulirkan halaman Anda sendiri.
un33k
3
Perhatikan bahwa pengaturan django-ipware tidak aman secara default! Siapa pun dapat melewati salah satu variabel lain dan situs Anda akan mencatat IP itu. Selalu setel IPWARE_META_PRECEDENCE_LISTke variabel yang Anda gunakan, atau gunakan alternatif seperti pypi.python.org/pypi/WsgiUnproxy
vdboor
@vdboor Bisakah Anda sedikit menguraikan? Saya tidak dapat menemukan IPWARE_META_PRECEDENCE_LIST dalam repo.
Monolith
2
@ThaJay Harap perhatikan bahwa pada 2.0.0, Anda harus menggunakan get_client_ip(). get_real_ipsudah usang dan akan dihapus dalam 3.0.
un33k
77

Jawaban Alexander bagus, tetapi tidak memiliki penanganan proxy yang terkadang mengembalikan beberapa IP di header HTTP_X_FORWARDED_FOR.

IP asli biasanya ada di akhir daftar, seperti dijelaskan di sini: http://en.wikipedia.org/wiki/X-Forwarded-For

Solusinya adalah modifikasi sederhana dari kode 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
sumber
5
Yup, ipnya ada di awal daftar. Ini salah.
Pykler
4
Sebenarnya, jika pengguna berada di belakang proxy Anda akan mendapatkan alamat IP internal pengguna, yaitu alamat RFC 1918. Dalam kebanyakan kasus, itu sama sekali tidak diinginkan. Solusi ini berfokus pada mendapatkan alamat IP eksternal klien (alamat proxy), yang merupakan alamat paling kanan.
Sævar
2
Terima kasih. Biasanya ketika saya meminta kunci dari request.METAsaya sertakan nilai default karena header sering salah:request.META.get('REMOTE_ADDR', None)
Carl G
2
@CarlG kode Anda lebih transparan, tetapi metode get diwarisi dari django.utils.datastructures.MultiValueDict dan nilai defaultnya adalah None. Tetapi tentu masuk akal untuk memasukkan nilai default jika Anda benar-benar menginginkannya selain dari Tidak Ada.
Sævar
2
Kecuali jika Anda menggosok X-Forwarded-For ketika permintaan mencapai server pertama Anda, maka nilai pertama dalam daftar itu adalah pengguna yang disediakan . Pengguna jahat dapat dengan mudah memalsukan alamat IP yang mereka inginkan. Alamat yang Anda inginkan adalah IP pertama sebelum server Anda, tidak harus yang pertama dalam daftar.
Eli
12

Saya ingin menyarankan peningkatan jawaban yanchenko.

Alih-alih mengambil ip pertama dalam daftar X_FORWARDED_FOR, saya mengambil ip pertama yang bukan ip internal yang dikenal, karena beberapa router tidak menghormati protokol, dan Anda dapat melihat ips internal sebagai nilai pertama dari daftar.

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

Saya harap ini membantu sesama Google yang memiliki masalah yang sama.

Doody P
sumber
Kode ini tidak memeriksa bahwa ip dari REMOTE_ADDR bersifat pribadi sebelum memeriksa bidang HTTP_X_FORWARDED_FOR, karena mungkin seharusnya (juga, '127.0.0.1' atau '127.' mungkin harus dalam PRIVATE_IPS_PREFIX, bersama dengan setara IPv6.
Rasmus Kaj
1
Secara teknis, awalan tersebut (172, 192) tidak selalu berarti alamat pribadi.
maniexx
2
Rentang alamat yang ditetapkan untuk jaringan pribadi adalah: 172.16.0.0–172.31.255.255 (16 “class B” networks), 192.168.0.0–192.168.255.255 (1 “class B” network) dan 10.0.0.0–10.255.255.255 (1 "Kelas A" atau 256 "kelas B" jaringan).
tzot
is_valid_ip tidak ditentukan
Prosenjit
7

di sini adalah liner pendek untuk mencapai ini:

request.META.get('HTTP_X_FORWARDED_FOR', request.META.get('REMOTE_ADDR', '')).split(',')[0].strip()
masterbase
sumber
3
Jika keduanya kembali Tidak ada maka Anda akan mendapatkan kesalahan.
Gourav Chawla
6

Solusi paling sederhana (jika Anda menggunakan fastcgi + nignx) adalah komentar itgorilla:

Terima kasih atas pertanyaan bagus ini. Fastcgi saya tidak melewati kunci meta REMOTE_ADDR. Saya menambahkan baris di bawah ini di nginx.conf dan memperbaiki masalahnya: fastcgi_param REMOTE_ADDR $ remote_addr; - itgorilla

PS: Saya menambahkan jawaban ini hanya untuk membuat solusinya lebih terlihat.

Juande Carrion
sumber
1
Apa solusi yang sebanding untuk nginx (reverse proxy) dan gunicorn? proxy_set_header REMOTE_ADDR $remote_addr;tidak mengurangi masalah ketika ditambahkan ke nginx.conf.
Hassan Baig
6

Tidak Ada lagi kebingungan Dalam versi terbaru Django disebutkan dengan jelas bahwa alamat IP klien tersedia di

request.META.get("REMOTE_ADDR")

untuk info lebih lanjut, periksa Django Docs

Pardhu
sumber
5

Dalam kasus saya tidak ada yang bekerja di atas, jadi saya harus memeriksa kode sumber uwsgi+ djangodan melewatkan param statis di nginx dan melihat mengapa / bagaimana, dan di bawah ini adalah apa yang saya temukan.

Info Env:
versi python : Versi 2.7.5
Django: (1, 6, 6, 'final', 0)
versi nginx: nginx/1.6.0
uwsgi:2.0.7

Env pengaturan info:
nginx sebagai reverse proxy listening di port 80 uwsgi sebagai hulu unix socket, akan merespons permintaan pada akhirnya

Info konfigurasi Django:

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

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

mendapatkan semua params di aplikasi Django:

X-Forwarded-For :       10.10.10.10
HTTP_X_FORWARDED_FOR :  20.20.20.20

Kesimpulan:

Jadi pada dasarnya, Anda harus menentukan bidang / nama param yang sama persis di nginx, dan menggunakannya request.META[field/param]di aplikasi Django.

Dan sekarang Anda dapat memutuskan apakah akan menambahkan middleware (interseptor) atau hanya menguraikan HTTP_X_FORWARDED_FORdalam pandangan tertentu.

xxmajia
sumber
2

Alasan fungsionalitas dihapus dari Django awalnya adalah bahwa header pada akhirnya tidak dapat dipercaya. Alasannya adalah bahwa itu mudah untuk dipalsukan. Misalnya, cara yang disarankan untuk mengonfigurasi proxy terbalik nginx adalah dengan:

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

Saat kamu melakukan:

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

Nginx Anda di myhost.com akan mengirimkan seterusnya:

X-Forwarded-For: 8.8.8.8, 192.168.1.2, 192.168.1.3

Ini X-Real-IPakan menjadi IP dari proksi sebelumnya pertama jika Anda mengikuti instruksi secara membabi buta.

Jika mempercayai siapa pengguna Anda merupakan masalah, Anda dapat mencoba sesuatu seperti django-xff: https://pypi.python.org/pypi/django-xff/

ferrix
sumber
1

Saya juga kehilangan proxy di jawaban di atas. Saya menggunakan get_ip_address_from_requestdari 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

Dan berikut adalah metode get_ip_address_from_request, IPv4 dan IPv6 siap:

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

Dalam django.VERSION (2, 1, 1, 'final', 0) request handler

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

jika Anda memanggil kode di atas dua kali, Anda mungkin mendapatkannya

AttributeError ("'_ io.BytesIO' objek tidak memiliki atribut 'stream'",)

AttributeError ("objek 'LimitedStream' tidak memiliki atribut 'raw'")

CS QGB
sumber