-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathverify.go
116 lines (102 loc) · 2.89 KB
/
verify.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
package verify
import (
"fmt"
"sync"
"time"
"github.com/chaitin/scaptcha-sdk-golang/utils"
"github.com/golang-jwt/jwt"
)
// 可能需要返回给用户业务的数据,v-id、score等
type VerifyClaims struct {
VerifyID string `json:"vid"`
}
// TokenVerifier 处理 JWT token 的验证和防重放
type TokenVerifier struct {
publicKey any
// publicKey *rsa.PublicKey
// 使用 sync.Map 替代普通 map,专门用于并发场景
usedTokens sync.Map
// 清理间隔
cleanupInterval time.Duration
// 停止清理的信号
stopCleanup chan struct{}
}
// NewTokenVerifier 创建新的 TokenVerifier
func NewTokenVerifier(publicKeyStr string) (*TokenVerifier, error) {
publicKey, err := utils.ParsePublicKey(utils.FormatPublicKey(publicKeyStr))
if err != nil {
return nil, fmt.Errorf("failed to parse RSA public key: %w", err)
}
v := &TokenVerifier{
publicKey: publicKey,
cleanupInterval: 5 * time.Second,
stopCleanup: make(chan struct{}),
}
go v.startCleanupRoutine()
return v, nil
}
// startCleanupRoutine 启动定期清理过期 token 的 goroutine
func (v *TokenVerifier) startCleanupRoutine() {
ticker := time.NewTicker(v.cleanupInterval)
defer ticker.Stop()
for {
select {
case <-ticker.C:
v.cleanup(time.Now().Unix())
case <-v.stopCleanup:
return
}
}
}
// cleanup 清理过期的 token
func (v *TokenVerifier) cleanup(now int64) {
v.usedTokens.Range(func(key, value interface{}) bool {
if expTime, ok := value.(int64); ok && expTime < now {
v.usedTokens.Delete(key)
}
return true
})
}
// Stop 停止清理 routine
func (v *TokenVerifier) Stop() {
close(v.stopCleanup)
}
// VerifyToken 验证 token 并防止重放攻击
func (v *TokenVerifier) VerifyToken(tokenString string) (bool, VerifyClaims, error) {
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return v.publicKey, nil
})
if err != nil {
return false, VerifyClaims{}, fmt.Errorf("failed to parse token: %w", err)
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
return false, VerifyClaims{}, fmt.Errorf("failed to get claims")
}
// 验证过期时间
expClaim, ok := claims["exp"].(float64)
if !ok {
err = fmt.Errorf("exp claim not found or invalid type")
return false, VerifyClaims{}, err
}
exp := time.Unix(int64(expClaim), 0)
if err != nil {
return false, VerifyClaims{}, fmt.Errorf("exp not found")
}
if exp.Unix() < time.Now().Unix() {
return false, VerifyClaims{}, fmt.Errorf("token expired")
}
// 验证并记录 verify id 防止重放
verifyID, ok := claims["vid"].(string)
if !ok {
return false, VerifyClaims{}, fmt.Errorf("invalid verify id")
}
// 使用 LoadOrStore 原子性地检查和存储
if _, loaded := v.usedTokens.LoadOrStore(verifyID, exp.Unix()); loaded {
return false, VerifyClaims{}, fmt.Errorf("token already used")
}
verifyClaims := VerifyClaims{
VerifyID: verifyID,
}
return true, verifyClaims, nil
}