feat(security): 增强访问控制安全性,支持可信代理配置

- 新增 TrustedProxies 配置项,安全解析 X-Forwarded-For
- 防止 IP 伪造攻击,仅信任来自可信代理的头部
- 使用右侧非可信 IP 作为真实客户端 IP
- 改进连接数限制中间件集成

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
xfy 2026-04-03 16:57:32 +08:00
parent ae8ea1ce0c
commit 98736e1f1c
4 changed files with 88 additions and 28 deletions

View File

@ -146,9 +146,10 @@ type SecurityConfig struct {
// AccessConfig IP 访问控制配置。
type AccessConfig struct {
Allow []string `yaml:"allow"` // 允许的 IP/CIDR 列表
Deny []string `yaml:"deny"` // 拒绝的 IP/CIDR 列表
Default string `yaml:"default"` // 默认动作allow 或 deny
Allow []string `yaml:"allow"` // 允许的 IP/CIDR 列表
Deny []string `yaml:"deny"` // 拒绝的 IP/CIDR 列表
Default string `yaml:"default"` // 默认动作allow 或 deny
TrustedProxies []string `yaml:"trusted_proxies"` // 可信代理 CIDR 列表,用于 X-Forwarded-For 解析
}
// RateLimitConfig 速率限制配置。

View File

@ -59,6 +59,9 @@ type AccessControl struct {
// defaultAction 默认操作,当无规则匹配时执行
defaultAction Action
// trustedProxies 可信代理 CIDR 列表,用于安全解析 X-Forwarded-For
trustedProxies []net.IPNet
// mu 保护并发访问的读写锁
mu sync.RWMutex
}
@ -98,6 +101,15 @@ func NewAccessControl(cfg *config.AccessConfig) (*AccessControl, error) {
ac.denyList = append(ac.denyList, *network)
}
// 解析可信代理列表
for _, cidr := range cfg.TrustedProxies {
network, err := parseCIDR(cidr)
if err != nil {
return nil, fmt.Errorf("invalid trusted_proxy CIDR %s: %w", cidr, err)
}
ac.trustedProxies = append(ac.trustedProxies, *network)
}
// 设置默认操作
switch strings.ToLower(cfg.Default) {
case "allow", "":
@ -130,7 +142,7 @@ func (ac *AccessControl) Name() string {
// - fasthttp.RequestHandler: 包装后的处理器
func (ac *AccessControl) Process(next fasthttp.RequestHandler) fasthttp.RequestHandler {
return func(ctx *fasthttp.RequestCtx) {
clientIP := getClientIP(ctx)
clientIP := ac.getClientIP(ctx)
// 检查访问权限
if !ac.Check(clientIP) {
@ -287,37 +299,83 @@ func parseCIDR(cidr string) (*net.IPNet, error) {
return network, nil
}
// getClientIP 从请求上下文提取客户端 IP。
// getClientIP 从请求上下文安全提取客户端 IP。
//
// 按优先级依次检查X-Forwarded-For、X-Real-IP、RemoteAddr。
// 仅当请求来自可信代理时,才解析 X-Forwarded-For 头部。
// 使用右侧(最接近客户端)的非可信 IP 作为真实客户端 IP。
//
// 参数:
// - ctx: FastHTTP 请求上下文
//
// 返回值:
// - net.IP: 客户端 IP 地址,无法获取时返回 nil
func getClientIP(ctx *fasthttp.RequestCtx) net.IP {
// 优先检查 X-Forwarded-For 头部
if xff := ctx.Request.Header.Peek("X-Forwarded-For"); len(xff) > 0 {
ips := strings.Split(string(xff), ",")
if len(ips) > 0 {
ipStr := strings.TrimSpace(ips[0])
ip := net.ParseIP(ipStr)
if ip != nil {
return ip
func (ac *AccessControl) getClientIP(ctx *fasthttp.RequestCtx) net.IP {
remoteIP := getRemoteAddrIP(ctx)
// 仅当配置了可信代理且请求来自可信代理时,才解析 X-Forwarded-For
if len(ac.trustedProxies) > 0 && remoteIP != nil {
isTrusted := false
for _, network := range ac.trustedProxies {
if network.Contains(remoteIP) {
isTrusted = true
break
}
}
if isTrusted {
// 使用右侧(最接近客户端)的非可信 IP
if xff := ctx.Request.Header.Peek("X-Forwarded-For"); len(xff) > 0 {
ips := strings.Split(string(xff), ",")
for i := len(ips) - 1; i >= 0; i-- {
ipStr := strings.TrimSpace(ips[i])
if ip := net.ParseIP(ipStr); ip != nil {
// 检查该 IP 是否在可信代理列表中
trusted := false
for _, network := range ac.trustedProxies {
if network.Contains(ip) {
trusted = true
break
}
}
if !trusted {
return ip
}
}
}
}
}
}
// 检查 X-Real-IP 头部
if xri := ctx.Request.Header.Peek("X-Real-IP"); len(xri) > 0 {
ip := net.ParseIP(string(xri))
if ip != nil {
return ip
// 检查 X-Real-IP 头部(仅来自可信代理时)
if len(ac.trustedProxies) > 0 && remoteIP != nil {
isTrusted := false
for _, network := range ac.trustedProxies {
if network.Contains(remoteIP) {
isTrusted = true
break
}
}
if isTrusted {
if xri := ctx.Request.Header.Peek("X-Real-IP"); len(xri) > 0 {
if ip := net.ParseIP(string(xri)); ip != nil {
return ip
}
}
}
}
// 回退到 RemoteAddr
return remoteIP
}
// getRemoteAddrIP 从 RemoteAddr 提取 IP。
//
// 参数:
// - ctx: FastHTTP 请求上下文
//
// 返回值:
// - net.IP: 客户端 IP 地址,无法获取时返回 nil
func getRemoteAddrIP(ctx *fasthttp.RequestCtx) net.IP {
if addr := ctx.RemoteAddr(); addr != nil {
if tcpAddr, ok := addr.(*net.TCPAddr); ok {
return tcpAddr.IP
@ -331,7 +389,6 @@ func getClientIP(ctx *fasthttp.RequestCtx) net.IP {
ipStr = strings.TrimPrefix(strings.TrimSuffix(ipStr, "]"), "[")
return net.ParseIP(ipStr)
}
return nil
}

View File

@ -28,6 +28,7 @@
package security
import (
"crypto/rand"
"encoding/base64"
"errors"
"fmt"
@ -559,10 +560,11 @@ func HashPasswordBcrypt(password string, cost int) (string, error) {
// - string: Argon2id 哈希字符串
// - error: 生成失败时返回错误
func HashPasswordArgon2id(password string, params argon2Params) (string, error) {
// 生成随机盐值
// 使用加密安全的随机数生成盐值
salt := make([]byte, params.saltLen)
// 注意:生产环境应使用 crypto/rand 生成盐值
// 此工具函数使用占位符方法
if _, err := rand.Read(salt); err != nil {
return "", fmt.Errorf("failed to generate salt: %w", err)
}
// 生成哈希
hash := argon2.IDKey([]byte(password), salt,

View File

@ -35,6 +35,7 @@ import (
"net"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/valyala/fasthttp"
@ -602,14 +603,13 @@ func (m *connLimiterMiddleware) Process(next fasthttp.RequestHandler) fasthttp.R
}
}
// 连接数原子操作辅助函数
// 连接数原子操作辅助函数
func loadInt64(ptr *int64) int64 {
return *ptr // 生产环境应使用 sync/atomic
return atomic.LoadInt64(ptr)
}
func addInt64(ptr *int64, delta int64) {
*ptr += delta // 简化实现;生产环境应使用 atomic.AddInt64
atomic.AddInt64(ptr, delta)
}
// 验证接口实现