fix(security): X-Forwarded-For IP 伪造防护
- isTrustedProxy: 空可信代理列表时默认不信任(安全优先) - realIP: 修正 XFF 遍历逻辑,从右到左跳过可信代理,返回第一个不可信的客户端 IP - GetClientIP: 优先读取 IPFilterMiddleware 已验证的 client_ip,避免直接信任转发头
This commit is contained in:
@@ -64,20 +64,17 @@ func (m *IPFilterMiddleware) realIP(c *gin.Context) string {
|
|||||||
// X-Forwarded-For 可能包含代理链
|
// X-Forwarded-For 可能包含代理链
|
||||||
xff := c.GetHeader("X-Forwarded-For")
|
xff := c.GetHeader("X-Forwarded-For")
|
||||||
if xff != "" {
|
if xff != "" {
|
||||||
// 从右到左遍历(最右边是最后一次代理添加的)
|
parts := strings.Split(xff, ",")
|
||||||
for _, part := range strings.Split(xff, ",") {
|
// 从右到左遍历(最右边是离服务器最近的代理)
|
||||||
ip := strings.TrimSpace(part)
|
for i := len(parts) - 1; i >= 0; i-- {
|
||||||
|
ip := strings.TrimSpace(parts[i])
|
||||||
if ip == "" {
|
if ip == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// 检查是否是可信代理
|
if m.isTrustedProxy(ip) {
|
||||||
if !m.isTrustedProxy(ip) {
|
continue // 跳过可信代理
|
||||||
continue // 不是可信代理,跳过
|
|
||||||
}
|
|
||||||
// 是可信代理,检查是否为公网 IP
|
|
||||||
if !isPrivateIP(ip) {
|
|
||||||
return ip
|
|
||||||
}
|
}
|
||||||
|
return ip // 第一个不可信代理就是真实客户端
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,7 +94,7 @@ func (m *IPFilterMiddleware) realIP(c *gin.Context) string {
|
|||||||
// isTrustedProxy 检查 IP 是否在可信代理列表中
|
// isTrustedProxy 检查 IP 是否在可信代理列表中
|
||||||
func (m *IPFilterMiddleware) isTrustedProxy(ip string) bool {
|
func (m *IPFilterMiddleware) isTrustedProxy(ip string) bool {
|
||||||
if len(m.config.TrustedProxies) == 0 {
|
if len(m.config.TrustedProxies) == 0 {
|
||||||
return true // 如果没有配置可信代理列表,默认信任所有(兼容旧行为)
|
return false // 未配置可信代理列表 → 不信任任何代理(安全优先)
|
||||||
}
|
}
|
||||||
for _, trusted := range m.config.TrustedProxies {
|
for _, trusted := range m.config.TrustedProxies {
|
||||||
if ip == trusted {
|
if ip == trusted {
|
||||||
|
|||||||
@@ -18,8 +18,12 @@ func init() {
|
|||||||
// newTestEngine 用给定的 IPFilterMiddleware 构建一个最简 Gin 引擎,
|
// newTestEngine 用给定的 IPFilterMiddleware 构建一个最简 Gin 引擎,
|
||||||
// 注册一个 GET /ping 路由,返回 client_ip 值。
|
// 注册一个 GET /ping 路由,返回 client_ip 值。
|
||||||
func newTestEngine(f *security.IPFilter) *gin.Engine {
|
func newTestEngine(f *security.IPFilter) *gin.Engine {
|
||||||
|
return newTestEngineWithConfig(f, IPFilterConfig{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestEngineWithConfig(f *security.IPFilter, cfg IPFilterConfig) *gin.Engine {
|
||||||
engine := gin.New()
|
engine := gin.New()
|
||||||
engine.Use(NewIPFilterMiddleware(f, IPFilterConfig{}).Filter())
|
engine.Use(NewIPFilterMiddleware(f, cfg).Filter())
|
||||||
engine.GET("/ping", func(c *gin.Context) {
|
engine.GET("/ping", func(c *gin.Context) {
|
||||||
ip, _ := c.Get("client_ip")
|
ip, _ := c.Get("client_ip")
|
||||||
c.JSON(http.StatusOK, gin.H{"ip": ip})
|
c.JSON(http.StatusOK, gin.H{"ip": ip})
|
||||||
|
|||||||
@@ -10,38 +10,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// GetClientIP 从 Gin Context 中提取客户端真实 IP 地址。
|
// GetClientIP 从 Gin Context 中提取客户端真实 IP 地址。
|
||||||
// 按以下优先级检查 Header:
|
// 优先读取 IPFilterMiddleware 设置的 client_ip(已做代理验证),
|
||||||
// 1. CF-Connecting-IP (Cloudflare)
|
// 否则回退到 c.ClientIP()(依赖 Gin 的 TrustedProxies 配置)。
|
||||||
// 2. X-Real-IP (Nginx)
|
|
||||||
// 3. X-Forwarded-For (取第一个非私有 IP)
|
|
||||||
// 4. c.ClientIP() (Gin 内置方法)
|
|
||||||
func GetClientIP(c *gin.Context) string {
|
func GetClientIP(c *gin.Context) string {
|
||||||
// 1. Cloudflare
|
if ip, ok := c.Get("client_ip"); ok {
|
||||||
if ip := c.GetHeader("CF-Connecting-IP"); ip != "" {
|
if s, ok := ip.(string); ok && s != "" {
|
||||||
return normalizeIP(ip)
|
return normalizeIP(s)
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Nginx X-Real-IP
|
|
||||||
if ip := c.GetHeader("X-Real-IP"); ip != "" {
|
|
||||||
return normalizeIP(ip)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. X-Forwarded-For (多个 IP 时取第一个公网 IP)
|
|
||||||
if xff := c.GetHeader("X-Forwarded-For"); xff != "" {
|
|
||||||
ips := strings.Split(xff, ",")
|
|
||||||
for _, ip := range ips {
|
|
||||||
ip = strings.TrimSpace(ip)
|
|
||||||
if ip != "" && !isPrivateIP(ip) {
|
|
||||||
return normalizeIP(ip)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 如果都是私有 IP,返回第一个
|
|
||||||
if len(ips) > 0 {
|
|
||||||
return normalizeIP(strings.TrimSpace(ips[0]))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Gin 内置方法
|
|
||||||
return normalizeIP(c.ClientIP())
|
return normalizeIP(c.ClientIP())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user