package verfyparam import ( "91porn-server/common/timeutil/timerange" "strconv" "strings" "time" "91porn-server/app/appg" "91porn-server/common" "91porn-server/common/constant" "91porn-server/common/constant/redisconst" "91porn-server/common/crypt" "91porn-server/common/log" "91porn-server/common/stderr" "github.com/gin-gonic/gin" ) var ( // 白名单,不需要验证token的api whitelist = map[string]bool{ "/swagger": true, "/api/app/ping/check": true, "/api/app/ping/pass": true, "/api/app/daichong": true, "/api/app/vid/sec": true, "/api/app/vid/new/sec": true, "/api/app/vid/lsjsec": true, // H.265 云转码使用 Handler 自己的路径绑定、限时 HMAC 鉴权。 "/api/app/vid/transcode/m3u8": true, "/api/app/im/whiteSign": true, "/api/app/im/sign": true, "/api/app/vid/upload": true, } ) // 内部使用 const InnerSecret string = "F^hgNT%MBpai+3qkz05NZtB5Ts@a_gRekEHJ3@KcF)T5>5.U7JLbx3!P1nxt#LhV" type Sign struct { Nonce string `json:"nonce"` TimeStamp string `json:"timestamp"` Path string `json:"path"` UserAgent string `json:"userAgent"` Token string `json:"token"` } func VerifyReplayAttackRequest(replayAttack appg.ReplayAttackConfig) gin.HandlerFunc { return func(ctx *gin.Context) { if !replayAttack.Enable { return } flag := common.IsGTESpecifyVer(ctx, constant.Ver3_6_0) if !flag { return } for url, ok := range whitelist { if ok && strings.HasPrefix(ctx.Request.URL.Path, url) { return } } var sign, timestamp, nonce string ua := ctx.Request.UserAgent() token := ctx.GetHeader("Authorization") apiKey := ctx.GetHeader("x-api-key") timestamp, sign, nonce = getXapiKey(apiKey) if sign == InnerSecret { return } t, _ := strconv.ParseInt(timestamp, 10, 64) if t == 0 || sign == "" || nonce == "" { ctx.Abort() common.ServeJSON(ctx, stderr.ErrInvalidRequestReplayAttack, nil) log.Info("VerifyReplayAttackRequest bad request UA", log.Any("apiKey", apiKey), log.Any("ip", ctx.ClientIP())) return } nowTime := time.Now().UTC().Unix() if nowTime > t+replayAttack.WindowDurationSeconds || nowTime < t-replayAttack.WindowDurationSeconds { ctx.Abort() common.ServeJSON(ctx, stderr.ErrInvalidRequestReplayAttack, nil) log.Info("VerifyReplayAttackRequest bad request time window", log.Any("apiKey", apiKey), log.Any("ip", ctx.ClientIP())) return } redisKey := redisconst.ReplayNonceKey(nonce) if appg.Redis.IsExist(redisKey) { ctx.Abort() common.ServeJSON(ctx, stderr.ErrInvalidRequestReplayAttack, nil) log.Info("VerifyReplayAttackRequest bad request dup nonce", log.Any("apiKey", apiKey), log.Any("ip", ctx.ClientIP())) return } s := Sign{ Nonce: nonce, TimeStamp: timestamp, Path: ctx.Request.URL.Path, UserAgent: ua, Token: token, } hmac := GeneratorSign(s, replayAttack.Key) if hmac != sign { ctx.Abort() common.ServeJSON(ctx, stderr.ErrInvalidRequestReplayAttack, nil) return } if err := appg.Redis.Set(redisKey, 1, 2*time.Duration(replayAttack.WindowDurationSeconds)*time.Second); err != nil { log.Info("VerifyReplayAttackRequest Save Redis Err", log.Any("key", redisKey), log.E(err)) } } } func CheckReplayAttackRequest() gin.HandlerFunc { return func(ctx *gin.Context) { if !appg.ShouldEnforceIPRateLimit() { return } for url, ok := range whitelist { if ok && strings.HasPrefix(ctx.Request.URL.Path, url) { return } } ua := ctx.Request.UserAgent() ip := common.GetIP(ctx) var now = time.Now() var recentSecond = timerange.RecentSecond(now, 10) checkApiKey := redisconst.CheckApiKey(ip, ctx.Request.URL.Path, recentSecond) if ua == "" { ctx.Abort() common.ServeJSON(ctx, stderr.ErrInvalidRequestReplayAttack, nil) log.InfoX(ctx, "CheckReplayAttackRequest bad request UA", log.Any("apiKey", checkApiKey), log.Any("ip", ip)) return } data, err := appg.Redis.Get(checkApiKey) if err != nil { log.InfoX(ctx, "CheckReplayAttackRequest get Redis Err", log.Any("key", checkApiKey), log.E(err)) } if data == nil { err := appg.Redis.Set(checkApiKey, "1", redisconst.CheckApiKeyExpire) if err != nil { log.InfoX(ctx, "CheckReplayAttackRequest Save Redis Err", log.Any("key", checkApiKey), log.E(err)) } } if data != nil { currentValue, err := strconv.ParseInt(*data, 10, 64) if err != nil { log.InfoX(ctx, "CheckReplayAttackRequest strconv.ParseInt Err", log.Any("key", checkApiKey), log.E(err)) } if currentValue > 10 { ctx.Abort() common.ServeJSON(ctx, stderr.ErrInvalidRequestReplayAttack, nil) log.InfoX(ctx, "CheckReplayAttackRequest bad request", log.Any("currentValue", currentValue), log.Any("apiKey", checkApiKey), log.Any("ip", ip)) return } currentValue += 1 setData := strconv.FormatInt(currentValue, 10) err = appg.Redis.Set(checkApiKey, setData, redisconst.CheckApiKeyExpire) if err != nil { log.InfoX(ctx, "CheckReplayAttackRequest set Redis Err", log.Any("key", checkApiKey), log.E(err)) } } ctx.Next() } } func getXapiKey(apiKey string) (timestamp, sign, nonce string) { if apiKey != "" { apiKeys := strings.Split(apiKey, ";") for _, v := range apiKeys { vss := strings.Split(strings.TrimSpace(v), "=") if len(vss) < 2 { log.Warn("x-api-key miss", log.Any("x-api-key", apiKey)) continue } switch strings.TrimSpace(vss[0]) { case "timestamp": timestamp = strings.TrimSpace(vss[1]) case "sign": sign = strings.TrimSpace(vss[1]) case "nonce": nonce = strings.TrimSpace(vss[1]) default: log.Warn("x-api-key miss", log.Any("x-api-key", apiKey)) } } } return } func GeneratorSign(s Sign, sec string) string { str, _ := crypt.StructToStr(s) return crypt.StrToHmacSha1(str, sec) }