Express.js:如何获取远程客户端地址

406

我不完全理解如何获取远程用户的IP地址。

假设我有一个简单的请求路由,例如:

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

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

上述方法获取真实用户IP地址是否正确,还有更好的方法吗?代理服务器如何处理?


3
按照**这里的说明,使用node-ipware**如何? - Avid Coder
如果您无法获取req.hostname,例如'example.com':https://dev59.com/Gmkw5IYBdhLWcg3wBmGA#37824880 - zhi.yang
1
可能是重复的问题:如何在Node中确定用户的IP地址 - Dan Dascalescu
1
我决定检查之前提到的 node-ipware(很久以前), 但现在已经被弃用了。建议使用 @fullerstack/nax-ipware 作为替代品,但其许可证明源代码是“专有和机密的”。 - contrebis
16个回答

707

如果您正在使用像NGiNX等代理服务器,则只需检查'x-forwarded-for'

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

如果代理不是你自己的,我就不会信任'x-forwarded-for'头部信息,因为它可以被伪造。


2
这是正确的,但在我的情况下,我必须使用方括号(请参见上面的编辑)。还要确保在您的nginx配置中启用了x-forwarded-for。非常好用! - ninehundreds
56
请注意,在使用自己的反向代理时,您需要在nginx配置中加入以下指令:proxy_set_header X-Forwarded-For $remote_addr;。请确保翻译后的内容意思保持不变,语言通俗易懂。 - Coxer
102
注意复制粘贴的用户:这可能返回一个逗号分隔的IP地址列表。我们曾经发现开发人员复制这个代码,并将结果与IP地址进行比较时出现了问题。建议使用类似于 var ip = (req.headers['x-forwarded-for'] || req.connection.remoteAddress || '').split(',')[0].trim(); 这样的代码来获取客户端的IP地址。 - Davy Jones
7
@Rishav ::1是本地主机的IPv6地址。 - Daniel O
3
@DavyJones的代码片段在typescript中无法工作,因为任何未列出的req.headers(包括“X-” headers)都被标记为string|string[]类型,不能使用split方法。需要使用类型保护对其进行重写。 - Alexander Uskov
显示剩余8条评论

321

@alessioalex的答案有效,但在Express - guide的“Express behind proxies”一节中还有另一种方法。

  1. app.set('trust proxy', true)添加到您的express初始化代码中。
  2. 当您想要获取远程客户端的IP时,以通常的方式使用req.ipreq.ips(就像没有反向代理一样)。

可选阅读:

  • 使用req.ipreq.ips。此解决方案不适用于req.connection.remoteAddress
  • 如果需要比信任通过x-forwarded-for头传递的所有内容更复杂的选项(例如,当您的代理不从不受信任的源删除现有的x-forwarded-for头时),可以使用更多'trust proxy'选项。有关详细信息,请参见链接的指南。
  • 如果您的代理服务器未填充x-forwarded-for头,则有两种可能性。
    1. 代理服务器未转发有关请求最初位置的信息。在这种情况下,将无法找出请求最初来自哪里。您需要首先修改代理服务器的配置。
      • 例如,如果您使用nginx作为反向代理,您可能需要将proxy_set_header X-Forwarded-For $remote_addr;添加到您的配置中。
    2. 代理服务器以专有方式(例如,自定义http头)传递请求最初位置的信息。在这种情况下,此答案将不起作用。可能有一种自定义方法可以获取该信息,但您需要首先了解机制。

8
我的回答比较通用(不限于Express),但如果你正在使用Express,那确实是更好的选择。 - alessioalex
4
代理服务器必须设置头部信息'x-forwarded-for'为远程地址。例如,在nginx中,你应该在配置文件中设置proxy_set_header X-Forwarded-For $remote_addr。 - Kamagatos
3
使用IISnode作为代理,通过在应用中调用app.enable('trust proxy'),也可以使用req.ip获取客户端的IP地址。但是使用这种方法获取的IP地址会包括端口号,如1.2.3.4:56789。为了去掉端口号,可以使用var ip = req.ip.split(':')[0] - Christiaan Westerbeek
4
我感觉这个答案缺乏安全性并需要更新。你应该始终定义应用程序信任哪些代理。所接受的答案至少在欺骗方面有一点提示。话虽如此,如果你正在使用express,使用这样的库确实是更好的解决方案,但引用的代码是不正确的,并且在链接的资源中找不到。 - Phil
5
为什么这给了我 ::1 - Rishav
显示剩余6条评论

75

nginx.conf 文件中:
proxy_set_header X-Real-IP $remote_addr;

node.js 服务器文件中:
var ip = req.headers['x-real-ip'] || req.connection.remoteAddress;

请注意,express会将头部字母变为小写


4
欢迎来到 Stack Overflow!请不要仅仅发布一大段代码,需要解释这段代码是如何解决所提出的问题的。没有解释的话,这不算是一个答案。 - Artemix
1
@ququzone的回答还不错。解释是在请求中设置一个自定义头部,名为“x-real-ip”,它获取访问者的原始IP地址。我用node和socket.io测试过了,很有效。 - coffekid
9
对我来说,IP地址可以在req.headers['x-real-ip']下获取,即使在nginx.conf中,标头使用大写字母设置。 - Nik Sumeiko
1
这就是解决我的问题的方法。即使将 trust-proxy 设置为 true,它仍然使用本地的 127.0.0.1 地址。 - David

43
如果您愿意使用第三方库,您可以检查request-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,代理1 IP,代理2 IP”的格式返回多个IP地址,因此我们取第一个) 3. CF-Connecting-IP(Cloudflare) 4. Fastly-Client-Ip(Fastly CDN和Firebase托管头部转发到云函数时) 5. True-Client-Ip(Akamai和Cloudflare) 6. X-Real-IP(Nginx代理/FastCGI) 7. X-Cluster-Client-IP(Rackspace LB,Riverbed Stingray) 8. X-Forwarded、Forwarded-For和Forwarded(#2的变体) 9. appengine-user-ip(Google App Engine) 10. req.connection.remoteAddress 11. req.socket.remoteAddress 12. req.connection.socket.remoteAddress 13. req.info.remoteAddress 14. Cf-Pseudo-IPv4(Cloudflare备用) 15. request.raw(Fastify)
如果找不到IP地址,将返回null。

声明:我与图书馆无关。


2
非常好的@hongbo!值得一提的是,由于Node区分大小写,如果想跳过中间件解决方案,应该使用req.headers['x-client-ip']req.headers['x-forwarded-for']等。 - Matthew

34

特别针对Node,http服务器组件的文档,在event connection下说:

当建立新的TCP流时触发。 socket是一个类型为net.Socket的对象。通常用户不会想要访问此事件。特别地,由于协议解析器依附于socket,因此socket不会发出可读事件。也可以在request.connection处访问套接字。

所以,这意味着request.connection是一个socket,并且根据文档确实有一个socket.remoteAddress属性,该属性根据文档是:

远程IP地址的字符串表示形式。例如,'74.125.127.100'或'2001:4860:a005::68'。

在Express下,请求对象也是Node http请求对象的实例,因此这种方法仍然适用。

但是,在Express.js下,请求已经具有两个属性:req.ipreq.ips

req.ip

返回远程地址,或者启用"trust proxy"时 - 上游地址。

req.ips

"trust proxy"true时,解析"X-Forwarded-For"ip地址列表并返回一个数组,否则返回一个空数组。例如,如果值为"client,proxy1,proxy2",您将接收到数组["client","proxy1","proxy2"],其中"proxy2"是最远的下游。

根据我的理解,Express的req.ipreq.connection.remoteAddress更好,因为req.ip包含实际客户端IP(在Express中启用了受信任的代理),而另一个可能包含代理的IP地址(如果有)。这就是目前接受的答案建议如下的原因:

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

req.headers['x-forwarded-for']将等同于express的req.ip


req.connection现在已被标记为过时的。 - Stepan Yakovenko

24

3
这是正确的方法。有关详细信息,请参见https://expressjs.com/en/guide/behind-proxies.html。 - O. Jones
如此简单明了。喜欢简洁的回答。 - gilbert-v
只是分享一下,这种方法也在 express-rate-limit node package 的文档中提到,并且通过设置 app.set('trust proxy', true); 并引用 req.ip,我能够获得我网站的所有期望行为,该网站使用 Cloudflare 作为代理。 - user1063287
自己注意:根据这个答案,Cloudflare在每个请求中设置CF-Connecting-IPX-Forwarded-For头部 - M Imam Pratama

11

这只是对此答案的附加信息。

如果您正在使用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 后,您可以使用 req.headers ['x-real-ip'] || req.connection.remoteAddress 访问 node/express 应用程序路由中的 IP。

req.headers['x-real-ip'] 返回 undefined,而 req.connection.remoteAddress 返回 ::ffff:127.0.0.1。 - Jayna Tanawala

9

我知道这个问题已经有答案了,但是我来分享一下我是如何让我的工作的。

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

6
我为此编写了一个包,您可以将其用作express中间件。我的包在此处发布: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);
});

10
在链接到与您有关联的内容时,您必须在帖子中披露关联关系。如果不披露关联关系,则会被视为垃圾邮件。披露必须明确,但不需要正式(例如,对于您自己的个人内容:“在我的网站上……”,“在我的博客上……”等)。请参阅:何谓“良好”的自我推广?有关自我推广的一些提示和建议Stack Overflow的“垃圾邮件”的精确定义是什么?什么算是垃圾邮件? - Makyen
这可以通过将trust proxy设置为true并使用req.ip来处理。为什么要为此制作一个模块? - asciidude

4
根据Express反向代理指南,如果您正确配置了trust proxy,则req.ip已经考虑了反向代理。因此,它比从网络层获取且不知道代理的req.connection.remoteAddress更好。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接