|
|
@@ -16,7 +16,7 @@ type SecurityPolicy struct {
|
|
16
|
16
|
|
|
17
|
17
|
// 验证码提供者接口
|
|
18
|
18
|
type CaptchaProvider interface {
|
|
19
|
|
- Generate(ip string) (string, string, error)
|
|
|
19
|
+ Generate() (id string, content string, answer string, err error)
|
|
20
|
20
|
//Validate(ip, code string) bool
|
|
21
|
21
|
Expiration() time.Duration // 验证码过期时间, 应该小于 AttemptsWindow
|
|
22
|
22
|
Draw(content string) (string, error) // 绘制验证码
|
|
|
@@ -24,6 +24,7 @@ type CaptchaProvider interface {
|
|
24
|
24
|
|
|
25
|
25
|
// 验证码元数据
|
|
26
|
26
|
type CaptchaMeta struct {
|
|
|
27
|
+ Id string
|
|
27
|
28
|
Content string
|
|
28
|
29
|
Answer string
|
|
29
|
30
|
ExpiresAt time.Time
|
|
|
@@ -117,7 +118,7 @@ func (ll *LoginLimiter) RecordFailedAttempt(ip string) {
|
|
117
|
118
|
}
|
|
118
|
119
|
|
|
119
|
120
|
// 生成验证码
|
|
120
|
|
-func (ll *LoginLimiter) RequireCaptcha(ip string) (error, CaptchaMeta) {
|
|
|
121
|
+func (ll *LoginLimiter) RequireCaptcha() (error, CaptchaMeta) {
|
|
121
|
122
|
ll.mu.Lock()
|
|
122
|
123
|
defer ll.mu.Unlock()
|
|
123
|
124
|
|
|
|
@@ -125,23 +126,24 @@ func (ll *LoginLimiter) RequireCaptcha(ip string) (error, CaptchaMeta) {
|
|
125
|
126
|
return errors.New("no captcha provider available"), CaptchaMeta{}
|
|
126
|
127
|
}
|
|
127
|
128
|
|
|
128
|
|
- content, answer, err := ll.provider.Generate(ip)
|
|
|
129
|
+ id, content, answer, err := ll.provider.Generate()
|
|
129
|
130
|
if err != nil {
|
|
130
|
131
|
return err, CaptchaMeta{}
|
|
131
|
132
|
}
|
|
132
|
133
|
|
|
133
|
134
|
// 存储验证码
|
|
134
|
|
- ll.captchas[ip] = CaptchaMeta{
|
|
|
135
|
+ ll.captchas[id] = CaptchaMeta{
|
|
|
136
|
+ Id: id,
|
|
135
|
137
|
Content: content,
|
|
136
|
138
|
Answer: answer,
|
|
137
|
139
|
ExpiresAt: time.Now().Add(ll.provider.Expiration()),
|
|
138
|
140
|
}
|
|
139
|
141
|
|
|
140
|
|
- return nil, ll.captchas[ip]
|
|
|
142
|
+ return nil, ll.captchas[id]
|
|
141
|
143
|
}
|
|
142
|
144
|
|
|
143
|
145
|
// 验证验证码
|
|
144
|
|
-func (ll *LoginLimiter) VerifyCaptcha(ip, answer string) bool {
|
|
|
146
|
+func (ll *LoginLimiter) VerifyCaptcha(id, answer string) bool {
|
|
145
|
147
|
ll.mu.Lock()
|
|
146
|
148
|
defer ll.mu.Unlock()
|
|
147
|
149
|
|
|
|
@@ -151,20 +153,20 @@ func (ll *LoginLimiter) VerifyCaptcha(ip, answer string) bool {
|
|
151
|
153
|
}
|
|
152
|
154
|
|
|
153
|
155
|
// 获取并验证验证码
|
|
154
|
|
- captcha, exists := ll.captchas[ip]
|
|
|
156
|
+ captcha, exists := ll.captchas[id]
|
|
155
|
157
|
if !exists {
|
|
156
|
158
|
return false
|
|
157
|
159
|
}
|
|
158
|
160
|
|
|
159
|
161
|
// 清理过期验证码
|
|
160
|
162
|
if time.Now().After(captcha.ExpiresAt) {
|
|
161
|
|
- delete(ll.captchas, ip)
|
|
|
163
|
+ delete(ll.captchas, id)
|
|
162
|
164
|
return false
|
|
163
|
165
|
}
|
|
164
|
166
|
|
|
165
|
167
|
// 验证并清理状态
|
|
166
|
168
|
if answer == captcha.Answer {
|
|
167
|
|
- delete(ll.captchas, ip)
|
|
|
169
|
+ delete(ll.captchas, id)
|
|
168
|
170
|
return true
|
|
169
|
171
|
}
|
|
170
|
172
|
|
|
|
@@ -176,16 +178,6 @@ func (ll *LoginLimiter) DrawCaptcha(content string) (err error, str string) {
|
|
176
|
178
|
return
|
|
177
|
179
|
}
|
|
178
|
180
|
|
|
179
|
|
-func (ll *LoginLimiter) RemoveCaptcha(ip string) {
|
|
180
|
|
- ll.mu.Lock()
|
|
181
|
|
- defer ll.mu.Unlock()
|
|
182
|
|
-
|
|
183
|
|
- _, exists := ll.captchas[ip]
|
|
184
|
|
- if exists {
|
|
185
|
|
- delete(ll.captchas, ip)
|
|
186
|
|
- }
|
|
187
|
|
-}
|
|
188
|
|
-
|
|
189
|
181
|
// 清除记录窗口
|
|
190
|
182
|
func (ll *LoginLimiter) RemoveAttempts(ip string) {
|
|
191
|
183
|
ll.mu.Lock()
|
|
|
@@ -212,7 +204,6 @@ func (ll *LoginLimiter) CheckSecurityStatus(ip string) (banned bool, captchaRequ
|
|
212
|
204
|
|
|
213
|
205
|
// 清理过期数据
|
|
214
|
206
|
ll.pruneAttempts(ip, time.Now().Add(-ll.policy.AttemptsWindow))
|
|
215
|
|
- ll.pruneCaptchas(ip)
|
|
216
|
207
|
|
|
217
|
208
|
// 检查验证码要求
|
|
218
|
209
|
captchaRequired = len(ll.attempts[ip]) >= ll.policy.CaptchaThreshold
|
|
|
@@ -272,10 +263,10 @@ func (ll *LoginLimiter) pruneAttempts(ip string, cutoff time.Time) []time.Time {
|
|
272
|
263
|
return valid
|
|
273
|
264
|
}
|
|
274
|
265
|
|
|
275
|
|
-func (ll *LoginLimiter) pruneCaptchas(ip string) {
|
|
276
|
|
- if captcha, exists := ll.captchas[ip]; exists {
|
|
|
266
|
+func (ll *LoginLimiter) pruneCaptchas(id string) {
|
|
|
267
|
+ if captcha, exists := ll.captchas[id]; exists {
|
|
277
|
268
|
if time.Now().After(captcha.ExpiresAt) {
|
|
278
|
|
- delete(ll.captchas, ip)
|
|
|
269
|
+ delete(ll.captchas, id)
|
|
279
|
270
|
}
|
|
280
|
271
|
}
|
|
281
|
272
|
}
|
|
|
@@ -299,7 +290,7 @@ func (ll *LoginLimiter) cleanupExpired() {
|
|
299
|
290
|
}
|
|
300
|
291
|
|
|
301
|
292
|
// 清理验证码
|
|
302
|
|
- for ip := range ll.captchas {
|
|
303
|
|
- ll.pruneCaptchas(ip)
|
|
|
293
|
+ for id := range ll.captchas {
|
|
|
294
|
+ ll.pruneCaptchas(id)
|
|
304
|
295
|
}
|
|
305
|
296
|
}
|