This commit is contained in:
2026-02-12 13:36:24 +08:00
parent f400052f95
commit 47cbc5b3a5
2 changed files with 94 additions and 7 deletions

View File

@@ -1,6 +1,7 @@
package crypto
import (
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
@@ -10,6 +11,8 @@ import (
"strconv"
"strings"
"time"
"tyapi-server/internal/shared/interfaces"
)
const (
@@ -95,6 +98,57 @@ func VerifySignature(params map[string]string, secretKey string, timestamp int64
return nil
}
// VerifySignatureWithNonceCheck 验证HMAC-SHA256签名并检查nonce唯一性防止重放攻击
// params: 请求参数map包含signature字段
// secretKey: 签名密钥
// timestamp: 时间戳(秒)
// nonce: 随机字符串
// cache: 缓存服务用于存储已使用的nonce
// cacheKeyPrefix: 缓存键前缀
func VerifySignatureWithNonceCheck(
ctx context.Context,
params map[string]string,
secretKey string,
timestamp int64,
nonce string,
cache interfaces.CacheService,
cacheKeyPrefix string,
) error {
// 1. 先进行基础签名验证
if err := VerifySignature(params, secretKey, timestamp, nonce); err != nil {
return err
}
// 2. 检查nonce是否已被使用防止重放攻击
// 使用请求指纹phone+timestamp+nonce 作为唯一标识
phone := params["phone"]
if phone == "" {
return errors.New("手机号不能为空")
}
// 构建nonce唯一性检查的缓存键
nonceKey := fmt.Sprintf("%s:nonce:%s:%d:%s", cacheKeyPrefix, phone, timestamp, nonce)
// 检查nonce是否已被使用
exists, err := cache.Exists(ctx, nonceKey)
if err != nil {
// 缓存查询失败,记录错误但继续验证(避免缓存故障导致服务不可用)
return fmt.Errorf("检查nonce唯一性失败: %w", err)
}
if exists {
return errors.New("请求已被使用,请勿重复提交")
}
// 3. 将nonce标记为已使用TTL设置为时间戳容差+1分钟确保在容差范围内不会重复使用
ttl := time.Duration(SignatureTimestampTolerance+60) * time.Second
if err := cache.Set(ctx, nonceKey, true, ttl); err != nil {
// 记录错误但不影响验证流程(避免缓存故障导致服务不可用)
return fmt.Errorf("标记nonce已使用失败: %w", err)
}
return nil
}
// 自定义编码字符集不使用标准Base64字符集增加破解难度
// 使用自定义字符集:数字+大写字母排除易混淆的I和O+小写字母排除易混淆的i和l+特殊字符
const customEncodeCharset = "0123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz!@#$%^&*()_+-=[]{}|;:,.<>?"