package api import ( "net" "net/http" "strings" "github.com/gin-gonic/gin" "oci-portal/internal/service" ) // ctxRealIPKey 是真实IP中间件写入 gin.Context 的键。 const ctxRealIPKey = "realIP" // RealIPMiddleware 按安全设置解析客户端真实 IP 写入上下文, // 全链路(IP 限速 / 登录守卫 / 系统日志留痕)统一消费;gin 内置 // TrustedProxies 已禁用,信任边界完全由该配置决定。 func RealIPMiddleware(settings *service.SettingService) gin.HandlerFunc { return func(c *gin.Context) { c.Set(ctxRealIPKey, resolveRealIP(c.Request, settings.SecurityCached().RealIPHeader)) c.Next() } } // requestIP 取中间件解析的真实 IP;未经中间件(单测直调 handler)时回退 RemoteAddr。 func requestIP(c *gin.Context) string { if ip := c.GetString(ctxRealIPKey); ip != "" { return ip } return remoteHost(c.Request) } // resolveRealIP 依配置头解析:X-Forwarded-For 取链尾(直连反代的对端, // 反代会把对端追加到链尾,链首可被客户端伪造);X-Real-IP 等单值头直取; // 头缺失或未配置(直连部署)回退 RemoteAddr。 func resolveRealIP(r *http.Request, header string) string { switch header { case "": case "X-Forwarded-For": if v := r.Header.Get(header); v != "" { parts := strings.Split(v, ",") if ip := strings.TrimSpace(parts[len(parts)-1]); ip != "" { return ip } } default: if ip := strings.TrimSpace(r.Header.Get(header)); ip != "" { return ip } } return remoteHost(r) } // remoteHost 取 RemoteAddr 的 host 部分。 func remoteHost(r *http.Request) string { host, _, err := net.SplitHostPort(r.RemoteAddr) if err != nil { return r.RemoteAddr } return host }