iris.context.request().remoteaddr 不可靠,因其返回的是反向代理(如nginx、cloudflare)的内网地址而非真实客户端ip;必须配置可信代理段、预处理x-forwarded-for/x-real-ip头,并通过中间件统一解析与校验ip后执行黑白名单控制。

为什么 iris.Context.Request().RemoteAddr 不可靠
直接用 iris.Context.Request().RemoteAddr 拿到的 IP 很可能是反向代理(Nginx、Cloudflare)的内网地址,比如 127.0.0.1 或 192.168.x.x,而不是真实客户端 IP。Iris 默认不自动解析 X-Forwarded-For 或 X-Real-IP,所以你写的黑白名单逻辑大概率失效。
必须显式配置信任代理段,并启用 iris.WithoutBodyConsumptionOnUnmarshal 以外的前置处理——更关键的是调用 ctx.RemoteAddr() 前先确保 ctx.Request().Header.Get("X-Forwarded-For") 被可信链路解析过。
- 若用 Nginx,需在配置中加
proxy_set_header X-Real-IP $remote_addr;和proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - Iris 启动时要传
iris.WithoutBodyConsumptionOnUnmarshal并手动设置app.WrapRouter中间件来预处理 header - 生产环境务必限制信任的代理 IP 段,避免伪造
X-Forwarded-For
用 middleware 实现可配置的黑白名单拦截
别在每个 handler 里重复判断,统一用中间件。核心是提前读取并标准化客户端 IP,再查白名单(或黑名单)集合。
示例代码片段:
var (
whiteList = map[string]struct{}{
"192.168.1.100": {},
"2001:db8::1": {},
}
)
app.Use(func(ctx iris.Context) {
ip := ctx.RemoteAddr()
// 如果你已配置好代理信任,这里才是真实客户端 IP
if ip == "" {
ip = ctx.Request().Header.Get("X-Real-IP")
if ip == "" {
ip = strings.Split(ctx.Request().Header.Get("X-Forwarded-For"), ",")[0]
}
}
ip = strings.TrimSpace(ip)
if _, ok := whiteList[ip]; !ok {
ctx.StatusCode(403)
ctx.WriteString("Forbidden")
return
}
ctx.Next()
})
- 注意:map 查找是 O(1),但白名单硬编码不便于热更新;建议改用
sync.Map或外部配置源(如 Redis) - IPv6 地址需保留原格式比对,不要做归一化(Iris 的
ctx.RemoteAddr()对 IPv6 返回带方括号的格式,如[::1]) - 若用黑名单模式,把
!ok改成ok即可,逻辑反转但性能一致
结合 iris.Host 结构做全局 IP 控制
如果黑白名单需要按域名或子路由区分(比如 admin.example.com 允许特定 IP,而 api.example.com 完全禁止某段),就不能只靠通用 middleware。得利用 Iris 的 iris.Host 或子路由器分组。
- 为不同 host 创建独立 app 实例:
adminApp := iris.New(),然后单独挂载 IP 控制中间件 - 或用
app.Party("/api").Use(ipMiddleware)绑定路径前缀 - 避免在
app.UseGlobal里写复杂分支逻辑——它会在所有请求上执行,包括静态文件和健康检查接口,容易误拦
特别注意:/health、/metrics 这类运维端点通常需要放行所有 IP,应在中间件中显式跳过,否则监控告警会失灵。
日志与调试时容易忽略的细节
上线后发现拦截失效?先确认三件事:
- 检查
ctx.RemoteAddr()输出是否和你预期一致,加一行log.Printf("IP: %s, X-Real-IP: %s, X-Forwarded-For: %s", ctx.RemoteAddr(), ctx.Request().Header.Get("X-Real-IP"), ctx.Request().Header.Get("X-Forwarded-For")) - 确认你的中间件注册顺序:必须在路由匹配之前执行(
app.Use(...)要早于app.Get(...)) - 如果用了
app.ConfigureContainer注入依赖,确保 IP 列表不是每次请求都重新加载——冷启动延迟或并发读写冲突会导致状态不一致
最隐蔽的问题是:某些 CDN 会把多个真实 IP 拼在 X-Forwarded-For 里(如 203.0.113.195, 70.41.3.18, 150.172.238.178),而你只取了第一个。是否取最左还是最右,取决于你信哪一层代理——这没有标准答案,得和基础设施团队对齐。











