// Package middleware 安全校验中间件 package middleware import ( "crypto/md5" "encoding/hex" "strconv" "time" "server/common" "server/config" "github.com/gin-gonic/gin" ) // SecurityMiddleware 安全校验中间件 // 防止暴力入侵,校验请求头签名 // 请求头需携带: // - X-App-Sign: 签名值 = MD5(timestamp + secretKey) // - X-App-Timestamp: 时间戳(毫秒) func SecurityMiddleware() gin.HandlerFunc { return func(c *gin.Context) { cfg := config.AppConfig.Security // 未启用则跳过 if !cfg.Enable { c.Next() return } // 白名单路径跳过 path := c.Request.URL.Path if isSecurityWhitelist(path) { c.Next() return } // 获取签名和时间戳 sign := c.GetHeader(cfg.HeaderKey) timestamp := c.GetHeader("X-App-Timestamp") if sign == "" || timestamp == "" { common.Warn("安全校验失败: 缺少签名头 IP=%s Path=%s", c.ClientIP(), path) common.Error(c, 403, "非法请求") c.Abort() return } // 验证时间戳 (5分钟内有效) ts, err := strconv.ParseInt(timestamp, 10, 64) if err != nil { common.Warn("安全校验失败: 时间戳格式错误 IP=%s", c.ClientIP()) common.Error(c, 403, "非法请求") c.Abort() return } now := time.Now().UnixMilli() if abs(now-ts) > 5*60*1000 { // 5分钟 common.Warn("安全校验失败: 时间戳过期 IP=%s Timestamp=%d", c.ClientIP(), ts) common.Error(c, 403, "请求已过期") c.Abort() return } // 验证签名 expectedSign := generateSign(timestamp, cfg.SecretKey) if sign != expectedSign { common.Warn("安全校验失败: 签名错误 IP=%s Sign=%s Expected=%s", c.ClientIP(), sign, expectedSign) common.Error(c, 403, "签名错误") c.Abort() return } c.Next() } } // generateSign 生成签名 func generateSign(timestamp, secretKey string) string { data := timestamp + secretKey hash := md5.Sum([]byte(data)) return hex.EncodeToString(hash[:]) } // 安全校验白名单 var securityWhitelist = []string{ "/swagger/", "/swagger/index.html", } func isSecurityWhitelist(path string) bool { for _, white := range securityWhitelist { if len(path) >= len(white) && path[:len(white)] == white { return true } } return false } func abs(n int64) int64 { if n < 0 { return -n } return n } // AddSecurityWhitelist 添加安全校验白名单 func AddSecurityWhitelist(paths ...string) { securityWhitelist = append(securityWhitelist, paths...) }