Express.js: วิธีรับที่อยู่ไคลเอนต์ระยะไกล


256

ฉันไม่เข้าใจวิธีการใช้ที่อยู่ IP ของผู้ใช้ระยะไกลอย่างสมบูรณ์

สมมติว่าฉันมีเส้นทางคำของ่าย ๆ เช่น:

app.get(/, function (req, res){
   var forwardedIpsStr = req.header('x-forwarded-for');
   var IP = '';

   if (forwardedIpsStr) {
      IP = forwardedIps = forwardedIpsStr.split(',')[0];  
   }
});

วิธีการข้างต้นถูกต้องหรือไม่ที่จะได้รับที่อยู่ IP ของผู้ใช้จริงหรือมีวิธีที่ดีกว่า แล้วพร็อกซี่ล่ะ?


1
วิธีการเกี่ยวกับการใช้โหนด ipwareตามคำอธิบายที่นี่
un33k

หากคุณไม่สามารถรับ req.hostname เช่น 'example.com': stackoverflow.com/a/37824880/5333284
zhi.yang

คำตอบ:


469

หากคุณทำงานอยู่หลังพร็อกซีเช่น NGiNX หรือมีอะไรคุณควรตรวจสอบ 'x-forwarded-for':

var ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;

หากพร็อกซีไม่ใช่ 'ของคุณ' ฉันจะไม่เชื่อถือส่วนหัว 'x-forwarded-for' เพราะจะสามารถปลอมแปลงได้


2
สิ่งนี้ถูกต้อง แต่ในสถานการณ์ของฉันฉันต้องใช้วงเล็บเหลี่ยม (ดูการแก้ไขด้านบน) ตรวจสอบให้แน่ใจว่าคุณมี x-forwarded- สำหรับเปิดใช้งานในการกำหนดค่า nginx ของคุณ ทำงานเหมือนจับใจ!
เก้าร้อย

43
คุณต้องจำไว้ว่าคุณต้องใส่คำสั่งนี้proxy_set_header X-Forwarded-For $remote_addr;ลงในการกำหนดค่า nginx ของคุณในกรณีที่คุณใช้ reverse proxy ของคุณเอง
Coxer

หากคุณวางแผนที่จะใช้ IP ในเบราว์เซอร์ http:: // [ipv6]: พอร์ตแจ้งให้ทราบว่าจำเป็นต้องมี [] หวังว่านี่จะช่วยได้
psuhas

43
ผู้ใช้งานคัดลอกพาสต้าโปรดทราบว่า: สิ่งนี้สามารถส่งคืนรายการที่อยู่ IP ที่คั่นด้วยเครื่องหมายจุลภาค เรามีข้อผิดพลาดจากการคัดลอกสิ่งนี้และเปรียบเทียบผลลัพธ์กับ IP บางทีทำสิ่งที่ต้องการvar ip = (req.headers['x-forwarded-for'] || req.connection.remoteAddress || '').split(',')[0].trim();รับ IP ไคลเอ็นต์
Davy Jones

3
@Rishav :: 1 เป็นที่อยู่ IPv6 สำหรับ localhost
Daniel O

238

ในขณะที่คำตอบจากผลงาน @alessioalex ที่มีวิธีการตามที่ระบุไว้ในอีกเอ็กซ์เพรสเบื้องหลังผู้รับมอบฉันทะส่วนExpress - คู่มือ

  1. เพิ่มapp.set('trust proxy', true)ไปยังรหัสเริ่มต้นด่วนของคุณ
  2. เมื่อคุณต้องการรับ ip ของไคลเอนต์ระยะไกลให้ใช้req.ipหรือreq.ipsตามปกติ (ราวกับไม่มีพร็อกซีย้อนกลับ)

อ่านเพิ่มเติม:

  • ใช้หรือreq.ip ใช้ไม่ได้กับโซลูชันนี้req.ipsreq.connection.remoteAddress
  • ตัวเลือกเพิ่มเติม'trust proxy'จะพร้อมใช้งานหากคุณต้องการบางสิ่งที่ซับซ้อนกว่าการเชื่อใจทุกสิ่งที่ผ่านในx-forwarded-forส่วนหัว (ตัวอย่างเช่นเมื่อพร็อกซีของคุณไม่ได้ลบส่วนหัวที่มีอยู่ก่อนหน้า x-forwarded-for จากแหล่งที่ไม่น่าเชื่อถือ) ดูคู่มือที่เชื่อมโยงสำหรับรายละเอียดเพิ่มเติม
  • หากพร็อกซีเซิร์ฟเวอร์ของคุณไม่ได้เติมx-forwarded-forส่วนหัวจะมีความเป็นไปได้สองอย่าง
    1. พร็อกซีเซิร์ฟเวอร์ไม่ส่งต่อข้อมูลว่าคำร้องขอมาจากที่ใด ในกรณีนี้จะไม่มีวิธีการค้นหาว่าคำขอมาจากที่ใด คุณต้องแก้ไขการกำหนดค่าพร็อกซีเซิร์ฟเวอร์ก่อน
      • ตัวอย่างเช่นหากคุณใช้ nginx เป็น reverse proxy คุณอาจต้องเพิ่มproxy_set_header X-Forwarded-For $remote_addr;การกำหนดค่าของคุณ
    2. พร็อกซีเซิร์ฟเวอร์ถ่ายทอดข้อมูลที่การร้องขอนั้นมาจากในรูปแบบกรรมสิทธิ์ (ตัวอย่างเช่นส่วนหัว http กำหนดเอง) ในกรณีเช่นนี้คำตอบนี้จะไม่ทำงาน อาจมีวิธีที่กำหนดเองในการดึงข้อมูลออกมา แต่คุณต้องเข้าใจกลไกก่อน

4
คำตอบของฉันกว้างขึ้น (ไม่ผูกติดกับ Express) แต่ถ้าคุณใช้ Express นั่นเป็นวิธีที่ดีกว่า
alessioalex

4
พร็อกซีเซิร์ฟเวอร์ของคุณต้องตั้งค่าส่วนหัว 'x-forwarded-for' เป็นที่อยู่ระยะไกล ในกรณีของ nginx คุณควรมี proxy_set_header X-Forwarded- สำหรับ $ remote_addr ในไฟล์
ปรับแต่ง

1
ด้านหลัง IIS พร้อม IISnode เป็นพร็อกซีapp.enable('trust proxy')ก็ใช้งานreq.ipได้เช่นกัน 1.2.3.4:56789ยกเว้นที่ฉันได้รับพอร์ตกับมัน เพื่อตัดสิ่งที่ฉันทำvar ip = req.ip.split(':')[0]
Christiaan Westerbeek

วิธีนี้ปลอดภัยหรือไม่
Daniel Kmak

3
ฉันรู้สึกว่าคำตอบนี้ขาดความปลอดภัยและต้องการการอัปเดต คุณควรกำหนดพร็อกซีที่แอปพลิเคชันของคุณเชื่อถือ คำตอบที่ได้รับการยอมรับอย่างน้อยก็มีข้อสังเกตเล็กน้อยเกี่ยวกับการปลอมแปลง ดังกล่าวเป็นวิธีที่ดีกว่าในการใช้ไลบรารี่แบบนี้ถ้าคุณใช้ express แต่โค้ดที่ยกมานั้นไม่ถูกต้องและไม่พบในทรัพยากรที่ถูกเชื่อมโยง
Phil

54

ในnginx.confไฟล์:
proxy_set_header X-Real-IP $remote_addr;

ในnode.jsไฟล์เซิร์ฟเวอร์:
var ip = req.headers['x-real-ip'] || req.connection.remoteAddress;

โปรดทราบว่าส่วนหัวของตัวพิมพ์เล็ก


3
ยินดีต้อนรับสู่ Stack Overflow! แทนที่จะโพสต์บล็อกโค้ดเท่านั้นโปรดอธิบายว่าเพราะเหตุใดโค้ดนี้จึงแก้ปัญหาที่เกิดขึ้น หากไม่มีคำอธิบายนี่ไม่ใช่คำตอบ
Artemix

1
คำตอบของ @ququzone ก็โอเค คำอธิบายถูกตั้งค่าส่วนหัวที่กำหนดเองในคำขอชื่อ "x-real-ip" ซึ่งใช้ที่อยู่ IP ดั้งเดิมจากผู้เข้าชม มันทำงานได้สำหรับฉันกับ node และ socket.io
coffekid

8
สำหรับฉันที่อยู่ IP จะอยู่ภายใต้req.headers['x-real-ip']แม้ในnginx.confส่วนหัวตั้งค่าด้วยตัวอักษรตัวใหญ่
Nik Sumeiko

นี่คือสิ่งที่แก้ไขได้สำหรับฉัน แม้จะตั้งค่าพร็อกซีไว้ใจให้เป็นจริง แต่ก็ยังคงใช้ที่อยู่ 127.0.0.1 ในพื้นที่
David

30

โดยเฉพาะอย่างยิ่งสำหรับโหนดเอกสารสำหรับส่วนประกอบเซิร์ฟเวอร์ http ภายใต้การเชื่อมต่อเหตุการณ์พูดว่า:

[ทริกเกอร์] เมื่อสร้างสตรีม TCP ใหม่ [The] ซ็อกเก็ตเป็นวัตถุประเภท netSocket โดยปกติผู้ใช้จะไม่ต้องการเข้าถึงกิจกรรมนี้ โดยเฉพาะอย่างยิ่งซ็อกเก็ตจะไม่ปล่อยเหตุการณ์ที่อ่านได้เนื่องจากวิธีการแยกวิเคราะห์โปรโตคอลแนบกับซ็อกเก็ต request.connectionซ็อกเก็ตนอกจากนี้ยังสามารถเข้าถึงได้

ดังนั้นนั่นหมายถึงrequest.connectionซ็อกเก็ตและตามเอกสารมีแน่นอนsocket.remoteAddressแอตทริบิวต์ซึ่งตามเอกสารประกอบคือ:

การแสดงสตริงของที่อยู่ IP ระยะไกล ตัวอย่างเช่น '74 .125.127.100 'หรือ' 2001: 4860: a005 :: 68 '

ภายใต้ด่วนวัตถุขอเป็นตัวอย่างของวัตถุคำขอ http http ดังนั้นวิธีนี้ยังคงทำงาน

อย่างไรก็ตามภายใต้ Express.js คำขอมีสองแอตทริบิวต์: req.ipและreq.ips

req.ip

ส่งคืนที่อยู่ระยะไกลหรือเมื่อเปิดใช้งาน "trust proxy" - ที่อยู่ต้นน้ำ

req.ips

เมื่อ"พร็อกซี่ไว้วางใจ"เป็นtrueแยก "X-Forwarded-For" รายการที่อยู่ IP และกลับอาร์เรย์มิฉะนั้นอาร์เรย์ที่ว่างเปล่าจะถูกส่งกลับ ตัวอย่างเช่นหากค่าเป็น "client, proxy1, proxy2" คุณจะได้รับอาร์เรย์ ["ลูกค้า", "proxy1", "proxy2"] โดยที่ "proxy2" เป็น down-stream ที่ไกลที่สุด

อาจเป็นมูลค่าการกล่าวขวัญว่าตามความเข้าใจของฉัน Express req.ipเป็นวิธีที่ดีกว่าreq.connection.remoteAddressเนื่องจากreq.ipมี IP ไคลเอ็นต์จริง (หากเปิดใช้งานพร็อกซีที่เชื่อถือได้ในด่วน) ในขณะที่อีกอันอาจมีที่อยู่ IP ของพร็อกซี (ถ้ามี หนึ่ง).

นั่นคือเหตุผลที่คำตอบที่ยอมรับในปัจจุบันแนะนำ:

var ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;

จะเทียบเท่าด่วนreq.headers['x-forwarded-for']req.ip



7

นี่เป็นเพียงข้อมูลเพิ่มเติมสำหรับคำตอบนี้

หากคุณใช้nginxงานอยู่คุณจะเพิ่มproxy_set_header X-Real-IP $remote_addr;ไปยังบล็อกตำแหน่งสำหรับไซต์ /etc/nginx/sites-available/www.example.comตัวอย่างเช่น. นี่คือบล็อกเซิร์ฟเวอร์ตัวอย่าง

server {
    listen 80;
    listen [::]:80;

    server_name example.com www.example.com;

    location / {
        proxy_set_header  X-Real-IP  $remote_addr;
        proxy_pass http://127.0.1.1:3080;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }
}

หลังจากรีสตาร์ทnginxคุณจะสามารถเข้าถึง IP ในเส้นทางnode/ expressแอปพลิเคชันของคุณด้วยreq.headers['x-real-ip'] || req.connection.remoteAddress;


3

ตามเอ็กซ์เพรสที่อยู่เบื้องหลังพร็อกซี่ , req.ipได้นำเข้าบัญชีพร็อกซี่กลับถ้าคุณได้กำหนดค่าtrust proxyอย่างถูกต้อง ดังนั้นจึงดีกว่าreq.connection.remoteAddressที่ได้รับจากเลเยอร์เครือข่ายและไม่ทราบถึงพรอกซี


3

ฉันเขียนแพ็คเกจเพื่อจุดประสงค์นั้น คุณสามารถใช้เป็นมิดเดิลแวร์ด่วน แพ็คเกจของฉันเผยแพร่ที่นี่: https://www.npmjs.com/package/express-ip

คุณสามารถติดตั้งโมดูลโดยใช้

npm i express-ip

การใช้

const express = require('express');
const app = express();
const expressip = require('express-ip');
app.use(expressip().getIpInfoMiddleware);

app.get('/', function (req, res) {
    console.log(req.ipInfo);
});

1
คุณต้องเปิดเผยความร่วมมือในโพสต์เมื่อเชื่อมโยงกับสิ่งที่คุณเป็นพันธมิตร หากคุณไม่เปิดเผยการเป็นพันธมิตรถือว่าเป็นสแปม การเปิดเผยจะต้องชัดเจน แต่ไม่จำเป็นต้องเป็นทางการ (เช่นสำหรับเนื้อหาส่วนบุคคลของคุณ: "ในเว็บไซต์ของฉัน ... ", "ในบล็อกของฉัน ... " ฯลฯ ) ดู: การส่งเสริมตัวเองของ "ดี" หมายถึงอะไร? , เคล็ดลับและคำแนะนำเกี่ยวกับการส่งเสริมตนเอง , ความหมายที่แท้จริงของ "สแปม" สำหรับกองมากเกินคืออะไร? และสิ่งที่ทำให้บางสิ่งบางอย่างสแปม
Makyen

2

ฉันรู้ว่าคำถามนี้ได้รับคำตอบแล้ว แต่นี่คือวิธีที่ฉันจะทำให้ฉันทำงาน

let ip = req.connection.remoteAddress.split(`:`).pop();

2

ถ้าคุณสบายดีใช้ห้องสมุดบุคคลที่สาม คุณสามารถตรวจสอบrequest-ipคำขอ-IP

คุณสามารถใช้มันโดย

import requestIp from 'request-ip';

app.use(requestIp.mw())

app.use((req, res) => {
  const ip = req.clientIp;
});

ซอร์สโค้ดค่อนข้างยาวดังนั้นฉันจะไม่คัดลอกที่นี่คุณสามารถตรวจสอบได้ที่https://github.com/pbojinov/request-ip/blob/master/src/index.js

โดยทั่วไป

ดูเหมือนว่าจะมีส่วนหัวเฉพาะในคำขอและกลับไปใช้ค่าเริ่มต้นบางส่วนหากไม่มีอยู่

IP ของผู้ใช้ถูกกำหนดโดยคำสั่งต่อไปนี้:

  1. X-Client-IP
  2. X-Forwarded-For (ส่วนหัวอาจส่งคืนที่อยู่ IP หลายแห่งในรูปแบบ: "ไคลเอนต์ IP, พร็อกซี 1 IP, พร็อกซี 2 IP" ดังนั้นเราจะใช้ที่อยู่แรก
  3. CF-Connecting-IP (Cloudflare)
  4. Fastly-Client-Ip (ส่วนหัวโฮสติ้ง CDN และ Firebase ที่รวดเร็วเมื่อเปลี่ยนไปใช้ฟังก์ชั่นคลาวด์)
  5. True-Client-Ip (Akamai และ Cloudflare)
  6. X-Real-IP (Nginx proxy / FastCGI)
  7. X-Cluster-Client-IP (Rackspace LB, ปลากระเบนแม่น้ำ)
  8. X-Forwarded, Forwarded-ForและForwarded(รูปแบบของ # 2)
  9. req.connection.remoteAddress
  10. req.socket.remoteAddress
  11. req.connection.socket.remoteAddress
  12. req.info.remoteAddress

ถ้าที่อยู่ IP nullไม่สามารถพบได้ก็จะกลับ

เปิดเผย: ฉันไม่ได้เกี่ยวข้องกับห้องสมุด


1

สิ่งนี้ได้ผลกับฉันดีกว่าที่เหลือ เว็บไซต์ของฉันอยู่เบื้องหลัง CloudFlare cf-connecting-ipและดูเหมือนว่ามันจะต้องใช้

req.headers['cf-connecting-ip'] || req.headers['x-forwarded-for'] || req.connection.remoteAddress

ไม่ได้ทดสอบExpress หลังผู้รับมอบฉันทะเนื่องจากไม่ได้พูดอะไรเกี่ยวกับcf-connecting-ipส่วนหัวนี้


1

ด้วยความสามารถแบบกระจายแสง, nginx และ x-real-ip

var user_ip;

    if(req.headers['cf-connecting-ip'] && req.headers['cf-connecting-ip'].split(', ').length) {
      let first = req.headers['cf-connecting-ip'].split(', ');
      user_ip = first[0];
    } else {
      let user_ip = req.headers['x-forwarded-for'] || req.headers['x-real-ip'] || req.connection.remoteAddress || req.socket.remoteAddress || req.connection.socket.remoteAddress;
    }

1

ในกรณีของฉันคล้ายกับโซลูชันนี้ฉันลงเอยด้วยการใช้วิธีการส่งต่อแบบ x-ต่อไปนี้:

let ip = (req.headers['x-forwarded-for'] || '').split(',')[0];

x-forwarded-forส่วนหัวจะทำการเพิ่มเส้นทางของ IP จากต้นทางไปจนถึงเซิร์ฟเวอร์ปลายทางสุดท้ายดังนั้นหากคุณต้องการดึง IP ของไคลเอนต์ต้นทางนี่จะเป็นรายการแรกของอาร์เรย์


0

var ip = req.connection.remoteAddress;

ip = ip.split (':') [3];


ouput เป็นเช่น: - :: ffff: XXX.XX.XX.XX จากนี้เราจะได้รับ ip
Bhulawat Ajay

3
ฉันคิดว่าip = ip.split(':').pop();จะปะทะในกรณีนี้ถ้า ip ปกติเช่น 127.0.0.1 จะมามันจะยังคงสามารถให้คุณ ip
9me

0

วัตถุส่วนหัวมีทุกสิ่งที่คุณต้องการเพียงทำสิ่งนี้:

var ip = req.headers['x-forwarded-for'].split(',')[0];

0

การรวมโซลูชัน witk @kakopappa เข้าด้วยกันพร้อมกับmorganการบันทึกที่อยู่ IP ของไคลเอ็นต์:

morgan.token('client_ip', function getId(req) {
    return req.client_ip
});
const LOG_OUT = ':remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent" :client_ip'
self.app.use(morgan(LOG_OUT, {
    skip: function(req, res) { // custom logging: filter status codes
        return res.statusCode < self._options.logging.statusCode;
    }
}));

// could-flare, nginx and x-real-ip support
var getIpInfoMiddleware = function(req, res, next) {
    var client_ip;
    if (req.headers['cf-connecting-ip'] && req.headers['cf-connecting-ip'].split(', ').length) {
        var first = req.headers['cf-connecting-ip'].split(', ');
        client_ip = first[0];
    } else {
        client_ip = req.headers['x-forwarded-for'] || req.headers['x-real-ip'] || req.connection.remoteAddress || req.socket.remoteAddress || req.connection.socket.remoteAddress;
    }
    req.client_ip = client_ip;
    next();
};
self.app.use(getIpInfoMiddleware);
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.