在使用 Gin 框架时,获取用户请求的真实 IP 地址涉及到多种情况,尤其在使用代理服务器(如 Nginx)时。使用 Gin 自带方法和其他方式获取用户 IP,以及在面对 Nginx 转发时如何准确获取客户端 IP,同时讨论与 IP 相关的安全问题及处理方法。
一、介绍
获取用户的真实 IP 地址是许多应用中必不可少的功能,用于识别用户、记录日志、进行安全控制等。在使用 Gin 框架时,我们需要了解如何正确获取用户的 IP,并处理可能涉及的安全问题。
二、使用 Gin 自带方法获取 IP
1、 使用 gin.Context
的 ClientIP
方法
Gin 框架提供了 gin.Context
对象的 ClientIP
方法,可直接获取客户端的 IP 地址。
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func getIP(c *gin.Context) {
ip := c.ClientIP()
c.JSON(http.StatusOK, gin.H{"ip": ip})
}
func main() {
router := gin.Default()
// 手机连电脑同一个局域网可测,即可获取手机网络的IP地址
router.GET("/getIp", getIP)
router.Run(":8080")
}
三、其他方式获取 IP
1、从 http.Request
中获取
案例一:
实际还是用了gin上下文
package main
import (
"github.com/gin-gonic/gin"
"net"
"net/http"
"strings"
)
func getIPFromRequest(c *gin.Context) {
// 获取客户端IP的函数,逻辑与之前相似
getIP := func(req *http.Request) string {
xForwardedFor := req.Header.Get("X-Forwarded-For")
if xForwardedFor != "" {
ipList := strings.Split(xForwardedFor, ",")
if len(ipList) > 0 {
return strings.TrimSpace(ipList[0])
}
}
xRealIP := req.Header.Get("X-Real-IP")
if xRealIP != "" {
return xRealIP
}
remoteAddr := req.RemoteAddr
ip, _, err := net.SplitHostPort(remoteAddr)
if err != nil {
return remoteAddr
}
return ip
}
clientIP := getIP(c.Request)
c.JSON(http.StatusOK, gin.H{"ip": clientIP})
}
func main() {
router := gin.Default()
// 手机连电脑同一个局域网可测,即可获取手机网络的IP地址
router.GET("/getIp", getIPFromRequest)
router.Run(":8080")
}
案例二:
使用 X-Real-IP 和 X-Forwarded-For 头部
当应用部署在代理服务器(如 Nginx)后面时,这些服务器通常会添加 X-Real-IP
或 X-Forwarded-For
头部,包含了真实的用户 IP 地址。
package main
import (
"fmt"
"net/http"
"strings"
)
func getClientIP(r *http.Request) string {
// 尝试从X-Forwarded-For获取
xForwardedFor := r.Header.Get("X-Forwarded-For")
if xForwardedFor != "" {
// 按逗号分割,取第一个IP地址
ips := strings.Split(xForwardedFor, ",")
if len(ips) > 0 {
return strings.TrimSpace(ips[0])
}
}
// 尝试从X-Real-IP获取
xRealIP := r.Header.Get("X-Real-IP")
if xRealIP != "" {
return strings.TrimSpace(xRealIP)
}
// 如果以上都没有,使用RemoteAddr
return r.RemoteAddr
}
func main() {
http.HandleFunc("/getIp", func(w http.ResponseWriter, r *http.Request) {
ip := getClientIP(r)
fmt.Fprint(w, ip)
})
http.ListenAndServe(":8080", nil)
}
案例三:
直接从 http.Request
对象中获取客户端 IP 地址,适用于非 Gin 框架的纯 Go 应用。
package main
import (
"encoding/json"
"net/http"
"strings"
)
// 定义响应结构体
type IPResponse struct {
IPAddress string `json:"ip"`
}
func getIPFromRequest(w http.ResponseWriter, r *http.Request) {
// 获取请求的 IP 地址,这里使用了 RemoteAddr 属性,它通常包含了端口号
ip := strings.Split(r.RemoteAddr, ":")[0]
// 设置响应的内容类型为 JSON
w.Header().Set("Content-Type", "application/json")
// 创建一个 IPResponse 实例并填充 IP 地址
response := IPResponse{IPAddress: ip}
// 使用标准库的 json 包将响应结构体编码为 JSON 格式
if err := json.NewEncoder(w).Encode(response); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
func main() {
// 设置 HTTP 路由处理器
http.HandleFunc("/getIp", getIPFromRequest)
// 启动 HTTP 服务器监听 8080 端口
if err := http.ListenAndServe(":8080", nil); err != nil {
panic(err)
}
}
案例四:
Nginx 转发配置
当应用部署在 Nginx 后面时,需要配置 Nginx 以确保正确传递用户 IP 地址。
# Nginx 配置文件中添加配置
server {
listen 80;
server_name your-server-name;
location / {
proxy_pass <http://your-gin-app-upstream>;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
四、安全问题及处理方法
防范 IP 欺骗攻击
问题描述: 攻击者可能伪造请求,冒充其他 IP 地址。
处理方法: 使用防火墙等措施,确保只有可信任的 IP 地址能够访问应用。
防范代理头欺骗
问题描述: 攻击者可能伪造代理头,发送虚假 IP。
处理方法: 在代码中仅信任可靠的代理头,并在获取 IP 前进行相应的验证。
package main
import (
"github.com/gin-gonic/gin"
"net/http"
"strings"
)
func getIPWithValidatedProxyHeaders(c *gin.Context) {
// 获取代理头部
proxyHeaders := c.Request.Header.Get("X-Real-IP,X-Forwarded-For")
// 分割代理头部,取第一个 IP 作为真实 IP
ips := strings.Split(proxyHeaders, ",")
ip := strings.TrimSpace(ips[0])
// 如果 IP 格式合法,则使用获取到的 IP,否则使用默认的 ClientIP 方法获取
if isValidIP(ip) {
c.JSON(http.StatusOK, gin.H{"ip": ip})
} else {
ip = c.ClientIP()
c.JSON(http.StatusOK, gin.H{"ip": ip})
}
}
// isValidIP 判断 IP 格式是否合法
func isValidIP(ip string) bool {
// 此处添加自定义的 IP 格式验证逻辑
// 例如,使用正则表达式验证 IP 格式
// ...
return true
}
func main() {
router := gin.Default()
router.GET("/getIp", getIPWithValidatedProxyHeaders)
router.Run(":8080")
}