首次提交:初始化项目代码
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
// Package security 安全模块,提供验证码、登录限流等安全防护能力
|
||||
package security
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/mojocn/base64Captcha"
|
||||
)
|
||||
|
||||
// CaptchaInstance 全局验证码实例
|
||||
var CaptchaInstance *base64Captcha.Captcha
|
||||
|
||||
// InitCaptcha 初始化数字验证码
|
||||
// @param width 验证码宽度
|
||||
// @param height 验证码高度
|
||||
// @author sunct
|
||||
func InitCaptcha(width, height int) {
|
||||
driver := base64Captcha.NewDriverDigit(
|
||||
height,
|
||||
width,
|
||||
4,
|
||||
0.7,
|
||||
80,
|
||||
)
|
||||
CaptchaInstance = base64Captcha.NewCaptcha(driver, base64Captcha.DefaultMemStore)
|
||||
}
|
||||
|
||||
// Verify 校验验证码答案
|
||||
// @param token 验证码令牌
|
||||
// @param code 用户输入的验证码
|
||||
// @return bool 是否验证通过
|
||||
// @author sunct
|
||||
func Verify(token, code string) bool {
|
||||
return base64Captcha.DefaultMemStore.Verify(token, code, true)
|
||||
}
|
||||
|
||||
// ServeCaptcha 生成并输出验证码图片(PNG格式)
|
||||
// @param w HTTP 响应写入器
|
||||
// @author sunct
|
||||
func ServeCaptcha(w http.ResponseWriter) {
|
||||
if CaptchaInstance == nil {
|
||||
InitCaptcha(160, 50)
|
||||
}
|
||||
|
||||
id, b64s, _ := CaptchaInstance.Generate()
|
||||
|
||||
w.Header().Set("Content-Type", "image/png")
|
||||
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||
w.Header().Set("Pragma", "no-cache")
|
||||
w.Header().Set("Expires", "0")
|
||||
w.Header().Set("X-Captcha-Token", id)
|
||||
|
||||
prefix := "data:image/png;base64,"
|
||||
if strings.HasPrefix(b64s, prefix) {
|
||||
b64s = strings.TrimPrefix(b64s, prefix)
|
||||
}
|
||||
|
||||
data, _ := base64.StdEncoding.DecodeString(b64s)
|
||||
w.Write(data)
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package security
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// LoginAttempt 登录尝试记录
|
||||
type LoginAttempt struct {
|
||||
Count int // 尝试次数
|
||||
LastTry time.Time // 最后尝试时间
|
||||
LockedUntil time.Time // 锁定截止时间
|
||||
}
|
||||
|
||||
// RateLimiter 登录限流器,基于 IP 防止暴力破解
|
||||
type RateLimiter struct {
|
||||
mu sync.Mutex // 互斥锁
|
||||
attempts map[string]*LoginAttempt // 尝试记录映射
|
||||
maxAttempts int // 最大尝试次数
|
||||
lockoutDuration time.Duration // 锁定时长
|
||||
}
|
||||
|
||||
// NewRateLimiter 创建登录限流器
|
||||
// @param maxAttempts 最大尝试次数
|
||||
// @param lockoutDuration 锁定时长(分钟)
|
||||
// @return *RateLimiter 限流器实例
|
||||
// @author sunct
|
||||
func NewRateLimiter(maxAttempts int, lockoutDuration int) *RateLimiter {
|
||||
return &RateLimiter{
|
||||
attempts: make(map[string]*LoginAttempt),
|
||||
maxAttempts: maxAttempts,
|
||||
lockoutDuration: time.Duration(lockoutDuration) * time.Minute,
|
||||
}
|
||||
}
|
||||
|
||||
// Allow 检查 IP 是否允许登录
|
||||
// @param ip 客户端IP
|
||||
// @return bool 是否允许
|
||||
// @return int 剩余尝试次数
|
||||
// @author sunct
|
||||
func (rl *RateLimiter) Allow(ip string) (bool, int) {
|
||||
rl.mu.Lock()
|
||||
defer rl.mu.Unlock()
|
||||
|
||||
now := time.Now()
|
||||
|
||||
if attempt, exists := rl.attempts[ip]; exists {
|
||||
if now.Before(attempt.LockedUntil) {
|
||||
return false, rl.maxAttempts - attempt.Count
|
||||
}
|
||||
|
||||
if now.Sub(attempt.LastTry) > 1*time.Hour {
|
||||
delete(rl.attempts, ip)
|
||||
return true, rl.maxAttempts
|
||||
}
|
||||
|
||||
if attempt.Count >= rl.maxAttempts {
|
||||
attempt.LockedUntil = now.Add(rl.lockoutDuration)
|
||||
return false, 0
|
||||
}
|
||||
}
|
||||
|
||||
return true, rl.maxAttempts
|
||||
}
|
||||
|
||||
// RecordAttempt 记录一次登录尝试
|
||||
// @param ip 客户端IP
|
||||
// @author sunct
|
||||
func (rl *RateLimiter) RecordAttempt(ip string) {
|
||||
rl.mu.Lock()
|
||||
defer rl.mu.Unlock()
|
||||
|
||||
now := time.Now()
|
||||
|
||||
if attempt, exists := rl.attempts[ip]; exists {
|
||||
attempt.Count++
|
||||
attempt.LastTry = now
|
||||
} else {
|
||||
rl.attempts[ip] = &LoginAttempt{
|
||||
Count: 1,
|
||||
LastTry: now,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset 重置 IP 的登录尝试记录
|
||||
// @param ip 客户端IP
|
||||
// @author sunct
|
||||
func (rl *RateLimiter) Reset(ip string) {
|
||||
rl.mu.Lock()
|
||||
defer rl.mu.Unlock()
|
||||
delete(rl.attempts, ip)
|
||||
}
|
||||
Reference in New Issue
Block a user