update
This commit is contained in:
76
internal/middleware/clientsign.go
Normal file
76
internal/middleware/clientsign.go
Normal file
@ -0,0 +1,76 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"fashionapi/internal/config"
|
||||
"fashionapi/internal/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
const (
|
||||
hdrSign = "X-Sign"
|
||||
hdrTs = "X-Sign-Ts"
|
||||
hdrNonce = "X-Sign-Nonce"
|
||||
)
|
||||
|
||||
// ClientSign 校验「公开接口」请求是否由前端 JS 签名(防简单爬虫/批量抓取的一层)。
|
||||
//
|
||||
// 校验内容:HMAC-SHA256(secret, METHOD + "\n" + Path + "\n" + RawQuery + "\n" + ts + "\n" + nonce)
|
||||
// 并要求 ts 在 ±TTL 秒以内(防重放)。
|
||||
//
|
||||
// 安全说明(务必知悉):
|
||||
// - 这是「提高成本」而非「加密」:secret 必须出现在前端 bundle 才能签名,因此本质上对浏览器可见,
|
||||
// 有决心的爬虫可反编译 JS 复刻签名逻辑。它用于拖慢 casual 爬虫,不能作为唯一防线。
|
||||
// - 真正有效的组合是:本中间件(识别「大概率真前端」)+ 按 IP 限流(兜住总量)。
|
||||
// - Enabled=false 或 Secret 为空时本中间件为 noop(不拦截),便于灰度上线与回滚。
|
||||
func ClientSign(cfg config.ClientSignConfig) gin.HandlerFunc {
|
||||
if !cfg.Enabled || cfg.Secret == "" {
|
||||
return func(c *gin.Context) { c.Next() }
|
||||
}
|
||||
ttl := cfg.TTLSeconds
|
||||
if ttl <= 0 {
|
||||
ttl = 30
|
||||
}
|
||||
secret := []byte(cfg.Secret)
|
||||
return func(c *gin.Context) {
|
||||
sig := c.GetHeader(hdrSign)
|
||||
ts := c.GetHeader(hdrTs)
|
||||
nonce := c.GetHeader(hdrNonce)
|
||||
if sig == "" || ts == "" || nonce == "" {
|
||||
response.AbortError(c, http.StatusUnauthorized, "missing client signature")
|
||||
return
|
||||
}
|
||||
ti, err := strconv.ParseInt(ts, 10, 64)
|
||||
if err != nil {
|
||||
response.AbortError(c, http.StatusUnauthorized, "invalid signature timestamp")
|
||||
return
|
||||
}
|
||||
now := time.Now().Unix()
|
||||
if diff := now - ti; diff < -int64(ttl) || diff > int64(ttl) {
|
||||
response.AbortError(c, http.StatusUnauthorized, "expired signature")
|
||||
return
|
||||
}
|
||||
mac := hmac.New(sha256.New, secret)
|
||||
mac.Write([]byte(strings.Join([]string{
|
||||
c.Request.Method,
|
||||
c.Request.URL.Path,
|
||||
c.Request.URL.RawQuery,
|
||||
ts,
|
||||
nonce,
|
||||
}, "\n")))
|
||||
expected := hex.EncodeToString(mac.Sum(nil))
|
||||
if !hmac.Equal([]byte(expected), []byte(sig)) {
|
||||
response.AbortError(c, http.StatusUnauthorized, "bad client signature")
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user