Iris 无内置 IP 黑名单中间件,但可通过 ctx.RemoteAddr() 结合自定义 map+sync.RWMutex 和 app.Use() 高效实现;需根据代理环境解析 X-Real-IP 或 X-Forwarded-For 获取真实 IP,黑名单查询用 map[string]struct{} 配读锁,拦截时调用 ctx.StopExecution() 并置顶中间件。

直接结论:Iris 没有内置 IP 黑名单中间件,但用 ctx.RemoteAddr() + 自定义 map/set + app.Use() 就能高效实现,无需第三方库。
怎么拿到真实客户端 IP?别只信 RemoteAddr()
默认 ctx.RemoteAddr() 返回的是直连 TCP 连接的地址(比如反向代理后就是 Nginx 的内网 IP),不是用户真实 IP。必须根据部署环境决定是否解析 X-Forwarded-For 或 X-Real-IP:
- 如果你用 Nginx 做反向代理,且配置了
proxy_set_header X-Real-IP $remote_addr;,就优先读ctx.GetHeader("X-Real-IP") - 如果用了 CDN 或多层代理,
X-Forwarded-For可能是逗号分隔的字符串,取第一个非私有地址(如跳过127.0.0.1、10.0.0.0/8) - 没代理时才 fallback 到
ctx.RemoteAddr(),并手动截掉端口(strings.Split(addr, ":")[0])
黑名单存储选 map[string]struct{},别用 slice 或 sync.Map
IP 黑名单查询是高频、只读为主的操作,用原生 map[string]struct{} 配合 sync.RWMutex 最轻量:
- 避免用
[]string—— 每次for遍历 O(n),1000 个 IP 就明显卡顿 - 别直接上
sync.Map—— 它适合写多读少场景,而黑名单极少更新,纯读性能反而不如带锁的普通 map - 示例结构:
var ( ipBlacklist = make(map[string]struct{}) blacklistMu sync.RWMutex ) func isBlocked(ip string) bool { blacklistMu.RLock() _, blocked := ipBlacklist[ip] blacklistMu.RUnlock() return blocked }
中间件里提前中断请求,别等进路由
IP 拦截必须在请求进入业务逻辑前完成,所以中间件要调用 ctx.StopExecution() 并返回状态码:
- 不能只写
ctx.StatusCode(403)就 return —— 后续 handler 仍会执行 - 正确姿势:
ctx.StatusCode(403); ctx.WriteString("Forbidden"); ctx.StopExecution() - 如果想记录日志,用
app.Logger().Warnf("Blocked IP: %s", ip),别用fmt.Println - 注意:该中间件应放在
app.Use()链最前面,否则可能被其他中间件(如 JWT 验证)先拦住,导致黑名单失效
动态更新黑名单时,记得加写锁并清空旧 map
运行时增删 IP 不能直接赋值新 map(引发并发读写 panic),必须用写锁保护:
- 添加:
blacklistMu.Lock() ipBlacklist[ip] = struct{}{} blacklistMu.Unlock() - 批量加载(如从文件或 DB):
newList := make(map[string]struct{}) // ... 填充 newList blacklistMu.Lock() ipBlacklist = newList blacklistMu.Unlock() - 容易忽略的点:没做锁保护的
delete(ipBlacklist, ip)在高并发下会 crash


















