从http.Request中获取客户端的IP地址的正确方法是什么?

huangapple go评论78阅读模式
英文:

Correct way of getting Client's IP Addresses from http.Request

问题

http.Request中获取所有客户端的IP地址的正确方法是什么?在PHP中,有很多变量我应该检查。在Go语言中是一样的吗?

我找到的一个方法是:

req.RemoteAddr

请求是否区分大小写?例如,x-forwarded-forX-Forwarded-ForX-FORWARDED-FOR是一样的吗?(使用req.Header.Get("X-FORWARDED-FOR")

英文:

What's the correct way to get all client's IP Addresses from http.Request? In PHP there are a lot of variables that I should check. Is it the same on Go?

One that I found is:

req.RemoteAddr

And is the request case sensitive? for example x-forwarded-for is the same as X-Forwarded-For and X-FORWARDED-FOR? (from req.Header.Get("X-FORWARDED-FOR"))

答案1

得分: 121

http.Request中,你可以找到以下成员变量:

// HTTP定义标头名称不区分大小写。
// 请求解析器通过将名称规范化来实现这一点,使第一个字符和连字符后的任何字符大写,其余字符小写。
//
// 对于客户端请求,某些标头会自动添加,并可能覆盖Header中的值。
//
// 请参阅Request.Write方法的文档。
Header Header

// RemoteAddr允许HTTP服务器和其他软件记录发送请求的网络地址,通常用于日志记录。
// ReadRequest不会填充此字段,并且没有定义的格式。此软件包中的HTTP服务器在调用处理程序之前将RemoteAddr设置为“IP:port”地址。
// HTTP客户端会忽略此字段。
RemoteAddr string

你可以使用RemoteAddr来获取远程客户端的IP地址和端口(格式为“IP:port”),这是原始请求者的地址或最后一个代理的地址(例如位于服务器前面的负载均衡器)。

这是你确切拥有的内容。

然后,你可以检查标头,它们是不区分大小写的(根据上面的文档),这意味着你的所有示例都将起作用并产生相同的结果:

req.Header.Get("X-Forwarded-For") // 大小写
req.Header.Get("x-forwarded-for") // 不区分大小写
req.Header.Get("X-FORWARDED-FOR") // 无关紧要

这是因为在内部,http.Header.Get会为你规范化键。(如果你想直接访问标头映射,而不是通过Get,你需要先使用http.CanonicalHeaderKey。)

最后,"X-Forwarded-For"可能是你想要查看的字段,以获取有关客户端IP的更多信息。但这在很大程度上取决于远程端使用的HTTP软件,因为客户端可以在其中放入任何内容。此外,请注意此字段的预期格式是逗号+空格分隔的IP地址列表。你需要对其进行一些解析,以获取你选择的单个IP(可能是列表中的第一个IP),例如:

// 假设格式符合预期
ips := strings.Split("10.0.0.1, 10.0.0.2, 10.0.0.3", ", ")
for _, ip := range ips {
fmt.Println(ip)
}

将产生:

10.0.0.1
10.0.0.2
10.0.0.3

英文:

Looking at http.Request you can find the following member variables:

// HTTP defines that header names are case-insensitive.
// The request parser implements this by canonicalizing the
// name, making the first character and any characters
// following a hyphen uppercase and the rest lowercase.
//
// For client requests certain headers are automatically
// added and may override values in Header.
//
// See the documentation for the Request.Write method.
Header Header

// RemoteAddr allows HTTP servers and other software to record
// the network address that sent the request, usually for
// logging. This field is not filled in by ReadRequest and
// has no defined format. The HTTP server in this package
// sets RemoteAddr to an "IP:port" address before invoking a
// handler.
// This field is ignored by the HTTP client.
RemoteAddr string

You can use RemoteAddr to get the remote client's IP address and port (the format is "IP:port"), which is the address of the original requestor or the last proxy (for example a load balancer which lives in front of your server).

This is all you have for sure.

Then you can investigate the headers, which are case-insensitive (per documentation above), meaning all of your examples will work and yield the same result:

req.Header.Get("X-Forwarded-For") // capitalisation
req.Header.Get("x-forwarded-for") // doesn't
req.Header.Get("X-FORWARDED-FOR") // matter

This is because internally http.Header.Get will normalise the key for you. (If you want to access header map directly, and not through Get, you would need to use http.CanonicalHeaderKey first.)

Finally, "X-Forwarded-For" is probably the field you want to take a look at in order to grab more information about client's IP. This greatly depends on the HTTP software used on the remote side though, as client can put anything in there if it wishes to. Also, note the expected format of this field is the comma+space separated list of IP addresses. You will need to parse it a little bit to get a single IP of your choice (probably the first one in the list), for example:

// Assuming format is as expected
ips := strings.Split("10.0.0.1, 10.0.0.2, 10.0.0.3", ", ")
for _, ip := range ips {
	fmt.Println(ip)
}

will produce:

10.0.0.1
10.0.0.2
10.0.0.3

答案2

得分: 38

这是我提供的IP获取方法:

func ReadUserIP(r *http.Request) string {
	IPAddress := r.Header.Get("X-Real-Ip")
	if IPAddress == "" {
		IPAddress = r.Header.Get("X-Forwarded-For")
	}
	if IPAddress == "" {
		IPAddress = r.RemoteAddr
	}
	return IPAddress
}
  • X-Real-Ip - 获取第一个真实的IP(如果请求位于多个NAT源/负载均衡器之后)
  • X-Forwarded-For - 如果X-Real-Ip为空并且没有返回结果,则从X-Forwarded-For获取
  • Remote Address - 最后的选择(通常不可靠,因为这可能是最后一个IP,或者如果是直接向服务器发出的裸HTTP请求,即没有负载均衡器)
英文:

This is how I come up with the IP

func ReadUserIP(r *http.Request) string {
	IPAddress := r.Header.Get("X-Real-Ip")
	if IPAddress == "" {
		IPAddress = r.Header.Get("X-Forwarded-For")
	}
	if IPAddress == "" {
		IPAddress = r.RemoteAddr
	}
	return IPAddress
}
  • X-Real-Ip - fetches first true IP (if the requests sits behind multiple NAT sources/load balancer)

  • X-Forwarded-For - if for some reason X-Real-Ip is blank and does not return response, get from X-Forwarded-For

  • Remote Address - last resort (usually won't be reliable as this might be the last ip or if it is a naked http request to server ie no load balancer)

答案3

得分: 35

这是一个完全可工作的示例:

package main

import (
	"fmt"
	"log"
	"net"
	"net/http"
	"strconv"

	"github.com/julienschmidt/httprouter"
	"github.com/skratchdot/open-golang/open"
)

// https://blog.golang.org/context/userip/userip.go
func getIP(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	fmt.Fprintf(w, "<h1>static file server</h1><p><a href='./static'>folder</p></a>")

	ip, port, err := net.SplitHostPort(req.RemoteAddr)
	if err != nil {
		fmt.Fprintf(w, "userip: %q is not IP:port", req.RemoteAddr)
	}

	userIP := net.ParseIP(ip)
	if userIP == nil {
		fmt.Fprintf(w, "userip: %q is not IP:port", req.RemoteAddr)
		return
	}

	// This will only be defined when site is accessed via non-anonymous proxy
	// and takes precedence over RemoteAddr
	// Header.Get is case-insensitive
	forward := req.Header.Get("X-Forwarded-For")

	fmt.Fprintf(w, "<p>IP: %s</p>", ip)
	fmt.Fprintf(w, "<p>Port: %s</p>", port)
	fmt.Fprintf(w, "<p>Forwarded for: %s</p>", forward)
}

func main() {
	myport := strconv.Itoa(10002)

	// Instantiate a new router
	r := httprouter.New()

	r.GET("/ip", getIP)

	// Add a handler on /test
	r.GET("/test", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		// Simply write some test data for now
		fmt.Fprint(w, "Welcome!\n")
	})

	l, err := net.Listen("tcp", "localhost:"+myport)
	if err != nil {
		log.Fatal(err)
	}

	// The browser can connect now because the listening socket is open.

	err = open.Start("http://localhost:"+myport+"/ip")
	if err != nil {
		log.Println(err)
	}

	// Start the blocking server loop.
	log.Fatal(http.Serve(l, r))
}

希望对你有帮助!

英文:

Here a completely working example

package main
import (  
// Standard library packages
&quot;fmt&quot;
&quot;strconv&quot;
&quot;log&quot;
&quot;net&quot;
&quot;net/http&quot;
// Third party packages
&quot;github.com/julienschmidt/httprouter&quot;
&quot;github.com/skratchdot/open-golang/open&quot;
)
// https://blog.golang.org/context/userip/userip.go
func getIP(w http.ResponseWriter, req *http.Request, _ httprouter.Params){
fmt.Fprintf(w, &quot;&lt;h1&gt;static file server&lt;/h1&gt;&lt;p&gt;&lt;a href=&#39;./static&#39;&gt;folder&lt;/p&gt;&lt;/a&gt;&quot;)
ip, port, err := net.SplitHostPort(req.RemoteAddr)
if err != nil {
//return nil, fmt.Errorf(&quot;userip: %q is not IP:port&quot;, req.RemoteAddr)
fmt.Fprintf(w, &quot;userip: %q is not IP:port&quot;, req.RemoteAddr)
}
userIP := net.ParseIP(ip)
if userIP == nil {
//return nil, fmt.Errorf(&quot;userip: %q is not IP:port&quot;, req.RemoteAddr)
fmt.Fprintf(w, &quot;userip: %q is not IP:port&quot;, req.RemoteAddr)
return
}
// This will only be defined when site is accessed via non-anonymous proxy
// and takes precedence over RemoteAddr
// Header.Get is case-insensitive
forward := req.Header.Get(&quot;X-Forwarded-For&quot;)
fmt.Fprintf(w, &quot;&lt;p&gt;IP: %s&lt;/p&gt;&quot;, ip)
fmt.Fprintf(w, &quot;&lt;p&gt;Port: %s&lt;/p&gt;&quot;, port)
fmt.Fprintf(w, &quot;&lt;p&gt;Forwarded for: %s&lt;/p&gt;&quot;, forward)
}
func main() {  
myport := strconv.Itoa(10002);
// Instantiate a new router
r := httprouter.New()
r.GET(&quot;/ip&quot;, getIP)
// Add a handler on /test
r.GET(&quot;/test&quot;, func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
// Simply write some test data for now
fmt.Fprint(w, &quot;Welcome!\n&quot;)
})	
l, err := net.Listen(&quot;tcp&quot;, &quot;localhost:&quot; + myport)
if err != nil {
log.Fatal(err)
}
// The browser can connect now because the listening socket is open.
//err = open.Start(&quot;http://localhost:&quot;+ myport + &quot;/test&quot;)
err = open.Start(&quot;http://localhost:&quot;+ myport + &quot;/ip&quot;)
if err != nil {
log.Println(err)
}
// Start the blocking server loop.
log.Fatal(http.Serve(l, r)) 
}

答案4

得分: 6

我认为我有一个比当前发布的方法更好的方法。

package main

import (
	"fmt"
	"log"
	"net"
	"net/http"
	"strings"
)

func main() {
	http.HandleFunc("/", getUserIP)
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		log.Fatal(err)
	}
}

// 获取服务器连接用户的IP地址。
func getUserIP(httpWriter http.ResponseWriter, httpServer *http.Request) {
	var userIP string
	if len(httpServer.Header.Get("CF-Connecting-IP")) > 1 {
		userIP = httpServer.Header.Get("CF-Connecting-IP")
		fmt.Println(net.ParseIP(userIP))
	} else if len(httpServer.Header.Get("X-Forwarded-For")) > 1 {
		userIP = httpServer.Header.Get("X-Forwarded-For")
		fmt.Println(net.ParseIP(userIP))
	} else if len(httpServer.Header.Get("X-Real-IP")) > 1 {
		userIP = httpServer.Header.Get("X-Real-IP")
		fmt.Println(net.ParseIP(userIP))
	} else {
		userIP = httpServer.RemoteAddr
		if strings.Contains(userIP, ":") {
			fmt.Println(net.ParseIP(strings.Split(userIP, ":")[0]))
		} else {
			fmt.Println(net.ParseIP(userIP))
		}
	}
}

这是你提供的代码的中文翻译。

英文:

I think I have a better way than the current method posted.

package main
import (
&quot;fmt&quot;
&quot;log&quot;
&quot;net&quot;
&quot;net/http&quot;
&quot;strings&quot;
)
func main() {
http.HandleFunc(&quot;/&quot;, getUserIP)
err := http.ListenAndServe(&quot;:8080&quot;, nil)
if err != nil {
log.Fatal(err)
}
}
// Get the IP address of the server&#39;s connected user.
func getUserIP(httpWriter http.ResponseWriter, httpServer *http.Request) {
var userIP string
if len(httpServer.Header.Get(&quot;CF-Connecting-IP&quot;)) &gt; 1 {
userIP = httpServer.Header.Get(&quot;CF-Connecting-IP&quot;)
fmt.Println(net.ParseIP(userIP))
} else if len(httpServer.Header.Get(&quot;X-Forwarded-For&quot;)) &gt; 1 {
userIP = httpServer.Header.Get(&quot;X-Forwarded-For&quot;)
fmt.Println(net.ParseIP(userIP))
} else if len(httpServer.Header.Get(&quot;X-Real-IP&quot;)) &gt; 1 {
userIP = httpServer.Header.Get(&quot;X-Real-IP&quot;)
fmt.Println(net.ParseIP(userIP))
} else {
userIP = httpServer.RemoteAddr
if strings.Contains(userIP, &quot;:&quot;) {
fmt.Println(net.ParseIP(strings.Split(userIP, &quot;:&quot;)[0]))
} else {
fmt.Println(net.ParseIP(userIP))
}
}
}

答案5

得分: 5

在PHP中,有很多变量需要检查。在Go语言中是否也是一样的?

这与Go语言(或PHP)无关。它只取决于客户端、代理、负载均衡器或服务器发送的内容。根据你的环境获取你需要的内容。

http.Request.RemoteAddr 包含远程 IP 地址。它可能是你实际的客户端,也可能不是。

请求是否区分大小写?例如,x-forwarded-for 和 X-Forwarded-For 以及 X-FORWARDED-FOR 是一样的吗?(来自 req.Header.Get("X-FORWARDED-FOR"))

不,为什么不自己试一下呢?可以在 http://play.golang.org/p/YMf_UBvDsH 进行尝试。

英文:

> In PHP there are a lot of variables that I should check. Is it the same on Go?

This has nothing to do with Go (or PHP for that matter). It just depends on what the client, proxy, load-balancer, or server is sending. Get the one you need depending on your environment.

http.Request.RemoteAddr contains the remote IP address. It may or may not be your actual client.

> And is the request case sensitive? for example x-forwarded-for is the same as X-Forwarded-For and X-FORWARDED-FOR? (from req.Header.Get("X-FORWARDED-FOR"))

No, why not try it yourself? http://play.golang.org/p/YMf_UBvDsH

答案6

得分: 4

根据Mozilla MDN的说法:“X-Forwarded-For(XFF)头是用于识别客户端的原始IP地址的事实标准头部。” 他们在他们的X-Forwarded-For文章中发布了清晰的信息。

英文:

According to Mozilla MDN: "The X-Forwarded-For (XFF) header is a de-facto standard header for identifying the originating IP address of a client."
They publish clear information in their X-Forwarded-For article.

答案7

得分: 3

客户端可以将X-Forwarded-For头设置为任意值。
在没有检查受信任的代理的情况下使用X-Forwarded-For可能导致IP欺骗。

头部示例:X-Forwarded-For: <client>, <proxy1>, <proxy2>

例如,有人可能会调用:

curl -H "X-Forwarded-For: 136.226.254.1" -H "X-Real-Ip: 136.226.254.2" "http://super.com"

如果你的L7负载均衡器不检查和清理这些头部,你的代码中将会出现IP欺骗(136.226.254.1)。如果你的逻辑基于客户端IP地址,它将无法正常工作。例如,基于IP地址进行限流。

例如,nginx模块http://nginx.org/ru/docs/http/ngx_http_realip_module.html使用基于获取X-Forwarded-For地址链中最后一个不受信任的IP地址的逻辑。我没有找到适用于Go的具有相同逻辑的正确中间件,但可以编写一个:https://github.com/thrownew/go-middlewares/tree/main/clientip

英文:

The client can set the X-Forwarded-For header to any arbitrary value it wants.
Usage X-Forwarded-For without check trusted proxies may lead to ip spoofing.

Header example: X-Forwarded-For: &lt;client&gt;, &lt;proxy1&gt;, &lt;proxy2&gt;

For example someone may call:

curl -H &quot;X-Forwarded-For: 136.226.254.1&quot; -H &quot;X-Real-Ip: 136.226.254.2&quot; &quot;http://super.com&quot;

If your L7 balancer don't check and cleanup this headers you will get ip spoofing in your code (136.226.254.1). If you have some logic based on client IP addresses, it won't work correctly. Throttling based on ip for example.

For example nginx module http://nginx.org/ru/docs/http/ngx_http_realip_module.html used logic based on getting last untrusted ip address in chain X-Forwarded-For addresses. I didn't find right middleware for go with same logic and write it: https://github.com/thrownew/go-middlewares/tree/main/clientip

答案8

得分: 1

当使用Cloudfront时,客户端的IP地址位于x-original-forwarded-for中。以下是一个JavaScript示例。

function getIp(request) {
  const { headers, connection, socket } = request
  const connectionSocket = connection && connection.socket

  return (
    (headers && headers['x-original-forwarded-for']) ||
    (connection && connection.remoteAddress) ||
    (socket && socket.remoteAddress) ||
    (connectionSocket && connectionSocket.remoteAddress) ||
    null
  )
}

请注意,这只是一个获取IP地址的函数示例,具体的实现可能会因环境和需求而有所不同。

英文:

When using Cloudfront the client IP address is in the x-original-forwarded-for. Below is a Javascript example.

function getIp(request) {
const { headers, connection, socket } = request
const connectionSocket = connection &amp;&amp; connection.socket
return (
(headers &amp;&amp; headers[&#39;x-original-forwarded-for&#39;]) ||
(connection &amp;&amp; connection.remoteAddress) ||
(socket &amp;&amp; socket.remoteAddress) ||
(connectionSocket &amp;&amp; connectionSocket.remoteAddress) ||
null
)
}

huangapple
  • 本文由 发表于 2014年12月2日 02:31:57
  • 转载请务必保留本文链接:https://go.coder-hub.com/27234861.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定