63 lines
1.7 KiB
Go
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
|
|
}
|