Files
oci-portal/internal/api/realip.go
T

63 lines
1.7 KiB
Go

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
}