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

View File

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

View File

@ -28,6 +28,7 @@
package security package security
import ( import (
"crypto/rand"
"encoding/base64" "encoding/base64"
"errors" "errors"
"fmt" "fmt"
@ -559,10 +560,11 @@ func HashPasswordBcrypt(password string, cost int) (string, error) {
// - string: Argon2id 哈希字符串 // - string: Argon2id 哈希字符串
// - error: 生成失败时返回错误 // - error: 生成失败时返回错误
func HashPasswordArgon2id(password string, params argon2Params) (string, error) { func HashPasswordArgon2id(password string, params argon2Params) (string, error) {
// 生成随机盐值 // 使用加密安全的随机数生成盐值
salt := make([]byte, params.saltLen) 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, hash := argon2.IDKey([]byte(password), salt,

View File

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