【Go】Получить реальный IP-адрес пользователя

Go

Оригинальная ссылка:blog.thinker idea.com/201903/go/…

Существует множество связей между запросом пользователя и сервером, предоставляющим услугу, что очень затрудняет получение сервисом реального IP-адреса пользователя.Большинство фреймворков и библиотек инструментов инкапсулируют различные методы для получения реального IP-адреса пользователя.exnetПакет также инкапсулирует различные операции, связанные с ip, в том числе способ получения ip клиента.Более практичные методы заключаются в следующем:

  • func ClientIP(r *http.Request) stringClientIP делает все возможное, чтобы реализовать алгоритм получения IP-адреса клиента. Разберите X-Real-IP и X-Forwarded-For, чтобы работал обратный прокси (nginx или haproxy).
  • func ClientPublicIP(r *http.Request) stringClientPublicIP максимально реализует алгоритм получения публичного IP клиента. Разберите X-Real-IP и X-Forwarded-For, чтобы работал обратный прокси (nginx или haproxy).
  • func HasLocalIP(ip net.IP) boolHasLocalIP проверяет, является ли IP-адрес адресом интрасети.
  • func HasLocalIPddr(ip string) boolHasLocalIPddr определяет, является ли строка IP-адреса адресом интрасети.
  • func RemoteIP(r *http.Request) stringRemoteIP получает IP-адрес через RemoteAddr, это просто метод быстрого разрешения.

Получить реальный IP-адрес пользователя

ClientIPметод сClientPublicIPРеализация метода аналогична, за исключением того, что один получает IP-адрес клиента в соответствии с соглашением по протоколу http, а другой находит IP-адрес общедоступной сети в соответствии с согласованным форматом.

В среде со сложной сетевой и сервисной архитектурой и бизнес-логикой не всегда возможно получить реальный ip по соглашению http протокола.В нашем бизнесе пользовательский трафик перенаправляется через три стороны и несколько уровней (все http клиенты реализованы самими тремя сторонами). ), неизбежно будут какие-то недочеты. В это время более позднему сервису сложнее получить реальный ip пользователя, и вы даже не знаете, настоящий ли ip, который вы получили .

Однако трафик перенаправляется нашими клиентами через три стороны, поэтому подавляющее большинство клиентов, даже исключенных из теста, являются пользователями общедоступной сети, комбинированное использованиеClientPublicIPа такжеClientIPМетод всегда лучше, чтобы получить реальный ip пользователя.

// var r *http.Request
ip := exnet.ClientPublicIP(r)
if ip == ""{
  ip = exnet.ClientIP(r)
}

Вышеупомянутый метод всегда может эффективно получить реальный IP-адрес пользователя.Конкретная реализация следующих двух методов анализируется ниже.

// ClientIP 尽最大努力实现获取客户端 IP 的算法。
// 解析 X-Real-IP 和 X-Forwarded-For 以便于反向代理(nginx 或 haproxy)可以正常工作。
func ClientIP(r *http.Request) string {
	xForwardedFor := r.Header.Get("X-Forwarded-For")
	ip := strings.TrimSpace(strings.Split(xForwardedFor, ",")[0])
	if ip != "" {
		return ip
	}

	ip = strings.TrimSpace(r.Header.Get("X-Real-Ip"))
	if ip != "" {
		return ip
	}

	if ip, _, err := net.SplitHostPort(strings.TrimSpace(r.RemoteAddr)); err == nil {
		return ip
	}

	return ""
}

ClientIPпрочитай сначалаX-Forwarded-Forиспользуется в заголовке,Первый ip адрес разделения, если такого адреса не существует, то он будет отправлен сX-Real-IpЕсли его по-прежнему нет, значит, трафик не перенаправляется обратным прокси, а клиент напрямую запрашивает сервис.http.Request.RemoteAddrПоле усекает IP-адрес с удаленным номером порта.

Этот метод очень прост, то есть получается по формату, согласованному http, гдеX-Forwarded-Forа такжеX-Real-IpЗаголовок заполняется обратными прокси, такими как nginx или haproxy.

// ClientPublicIP 尽最大努力实现获取客户端公网 IP 的算法。
// 解析 X-Real-IP 和 X-Forwarded-For 以便于反向代理(nginx 或 haproxy)可以正常工作。
func ClientPublicIP(r *http.Request) string {
	var ip string
	for _, ip = range strings.Split(r.Header.Get("X-Forwarded-For"), ",") {
		ip = strings.TrimSpace(ip)
		if ip != "" && !HasLocalIPddr(ip) {
			return ip
		}
	}

	ip = strings.TrimSpace(r.Header.Get("X-Real-Ip"))
	if ip != "" && !HasLocalIPddr(ip) {
		return ip
	}

	if ip, _, err := net.SplitHostPort(strings.TrimSpace(r.RemoteAddr)); err == nil {
		if !HasLocalIPddr(ip) {
			return ip
		}
	}

	return ""
}

ClientPublicIPочень просто, иClientIPПорядок чтения метода такой же, просто попробуйтеX-Forwarded-ForНайти публичный IP в списке, если нет галочкиX-Real-IpЯвляется ли это IP-адрес общедоступной сети, а затем проверьтеhttp.Request.RemoteAddrЯвляется ли это IP-адресом общедоступной сети, если IP-адрес общедоступной сети не найден, возвращается пустая строка.

Этот метод может дать нам возможность сначала получить общедоступный сетевой IP-адрес пользователя, а общедоступный сетевой IP-адрес часто более ценен для нас.

Проверьте, является ли пара IP-адресов адресом интрасети.

exnetОн также обеспечивает проверку того, является ли IP-адрес адресом интрасети, что очень полезно в некоторых случаях, например: некоторые интерфейсы в службе могут быть доступны только из интрасети, то есть доступ разрешен только администраторам (например, динамически установка уровней логов, просмотр сервисов pprof информации); мы хотим скрыть back-end сервис и выставить его только для балансировки нагрузки пользователя (обратный прокси), а пользователь не может получить прямой доступ к нашему сервису. Эти методы крайне полезны. Давайте возьмем посмотрите на конкретную реализацию.

Моя служба обеспечивает динамическую настройку уровня журнала, так что в случае возникновения проблемы со службой вы можете просмотреть журнал отладки для анализа конкретной причины, но этот интерфейс очень опасен и не должен быть доступен в общедоступной сети, поэтому промежуточное ПО маршрутизации будет использоваться для проверки того, поступает ли запрос из сети общего пользования, и возвращает ли 404 из сети общего пользования.

Этот метод предполагает, что все следующие сегменты адресов являются адресами интрасети:

10.0.0.0/8
169.254.0.0/16
172.16.0.0/12
172.17.0.0/12
172.18.0.0/12
172.19.0.0/12
172.20.0.0/12
172.21.0.0/12
172.22.0.0/12
172.23.0.0/12
172.24.0.0/12
172.25.0.0/12
172.26.0.0/12
172.27.0.0/12
172.28.0.0/12
172.29.0.0/12
172.30.0.0/12
172.31.0.0/12
192.168.0.0/16
// HasLocalIPddr 检测 IP 地址字符串是否是内网地址
func HasLocalIPddr(ip string) bool {
	return HasLocalIP(net.ParseIP(ip))
}

// HasLocalIP 检测 IP 地址是否是内网地址
func HasLocalIP(ip net.IP) bool {
	for _, network := range localNetworks {
		if network.Contains(ip) {
			return true
		}
	}

	return ip.IsLoopback()
}

Разница в реализации между двумя методами проверки заключается в том, что они принимают только несовместимые типы параметров.Процесс проверки заключается в сравнении того, содержит ли IP-сегмент интрасети IP-адрес один за другим.Если это не так, оценивается, является ли адрес петлевым. адрес.

Получить ip обратного прокси

Как судить, что измененный адрес от обратного прокси-сервера?Разные реализации обратного прокси несколько отличаются.4-х слойный обратный прокси может даже предоставить реальный айпи пользователя(http.Request.RemoteAddrЭто ip пользователя, а не обратный прокси) и скрывает свой собственный ip. Вот несколько распространенных способов.

довольно частоhttp.Request.RemoteAddrСохраняем последний клиентский ip подключенный к сервису, получаем ip адрес реверсивного прокси, самый простой и действенный способ это передатьhttp.Request.RemoteAddrПолучать,exnetпредоставлено вRemoteIPМетод быстрого доступа реализуется следующим образом:

// RemoteIP 通过 RemoteAddr 获取 IP 地址, 只是一个快速解析方法。
func RemoteIP(r *http.Request) string {
	if ip, _, err := net.SplitHostPort(strings.TrimSpace(r.RemoteAddr)); err == nil {
		return ip
	}

	return ""
}

Это очень удобная подставка, она просто нарезаетhttp.Request.RemoteAddrip и порт и вернуть действительный IP-адрес, но это может упростить написание нашего бизнес-кода.

Перепечатано:

Автор этой статьи: Ци Инь (thinkeridea)

Ссылка на эту статью:blog.thinker idea.com/201903/go/…

Заявление об авторских правах: все статьи в этом блоге используются, если не указано иное.Соглашение CC BY 4.0 CNсоглашение. Пожалуйста, укажите источник!