package admin import ( "encoding/json" "fmt" "io" "net" "net/http" "strings" "sync" "time" "github.com/bjdgyc/anylink/base" ) type LockInfo struct { Description string `json:"description"` // 锁定原因 Username string `json:"username"` // 用户名 IP string `json:"ip"` // IP 地址 State *LockState `json:"state"` // 锁定状态信息 } type LockState struct { Locked bool `json:"locked"` // 是否锁定 FailureCount int `json:"attempts"` // 失败次数 LockTime time.Time `json:"lock_time"` // 锁定截止时间 LastAttempt time.Time `json:"lastAttempt"` // 最后一次尝试的时间 } type IPWhitelists struct { IP net.IP CIDR *net.IPNet } type LockManager struct { mu sync.Mutex LoginStatus sync.Map // 登录状态 ipLocks map[string]*LockState // 全局IP锁定状态 userLocks map[string]*LockState // 全局用户锁定状态 ipUserLocks map[string]map[string]*LockState // 单用户IP锁定状态 ipWhitelists []IPWhitelists // 全局IP白名单,包含IP地址和CIDR范围 cleanupTicker *time.Ticker } var lockmanager *LockManager var once sync.Once func GetLockManager() *LockManager { once.Do(func() { lockmanager = &LockManager{ LoginStatus: sync.Map{}, ipLocks: make(map[string]*LockState), userLocks: make(map[string]*LockState), ipUserLocks: make(map[string]map[string]*LockState), ipWhitelists: make([]IPWhitelists, 0), } }) return lockmanager } const defaultGlobalLockStateExpirationTime = 3600 func InitLockManager() { lm := GetLockManager() if base.Cfg.AntiBruteForce { if base.Cfg.GlobalLockStateExpirationTime <= 0 { base.Cfg.GlobalLockStateExpirationTime = defaultGlobalLockStateExpirationTime } lm.StartCleanupTicker() lm.InitIPWhitelist() } } func GetLocksInfo(w http.ResponseWriter, r *http.Request) { lm := GetLockManager() locksInfo := lm.GetLocksInfo() RespSucess(w, locksInfo) } func UnlockUser(w http.ResponseWriter, r *http.Request) { body, err := io.ReadAll(r.Body) if err != nil { RespError(w, RespInternalErr, err) return } lockinfo := LockInfo{} if err := json.Unmarshal(body, &lockinfo); err != nil { RespError(w, RespInternalErr, err) return } if lockinfo.State == nil { RespError(w, RespInternalErr, fmt.Errorf("未找到锁定用户!")) return } lm := GetLockManager() lm.mu.Lock() defer lm.mu.Unlock() lm.Unlock(lockinfo.State) base.Info("解锁成功:", lockinfo.Description, lockinfo.Username, lockinfo.IP) RespSucess(w, "解锁成功!") } func (lm *LockManager) GetLocksInfo() []LockInfo { var locksInfo []LockInfo lm.mu.Lock() defer lm.mu.Unlock() for ip, state := range lm.ipLocks { if state.Locked { info := LockInfo{ Description: "全局 IP 锁定", Username: "", IP: ip, State: &LockState{ Locked: state.Locked, FailureCount: state.FailureCount, LockTime: state.LockTime, LastAttempt: state.LastAttempt, }, } locksInfo = append(locksInfo, info) } } for username, state := range lm.userLocks { if state.Locked { info := LockInfo{ Description: "全局用户锁定", Username: username, IP: "", State: &LockState{ Locked: state.Locked, FailureCount: state.FailureCount, LockTime: state.LockTime, LastAttempt: state.LastAttempt, }, } locksInfo = append(locksInfo, info) } } for username, ipStates := range lm.ipUserLocks { for ip, state := range ipStates { if state.Locked { info := LockInfo{ Description: "单用户 IP 锁定", Username: username, IP: ip, State: &LockState{ Locked: state.Locked, FailureCount: state.FailureCount, LockTime: state.LockTime, LastAttempt: state.LastAttempt, }, } locksInfo = append(locksInfo, info) } } } return locksInfo } // 初始化IP白名单 func (lm *LockManager) InitIPWhitelist() { ipWhitelist := strings.Split(base.Cfg.IPWhitelist, ",") for _, ipWhitelist := range ipWhitelist { ipWhitelist = strings.TrimSpace(ipWhitelist) if ipWhitelist == "" { continue } _, ipNet, err := net.ParseCIDR(ipWhitelist) if err == nil { lm.ipWhitelists = append(lm.ipWhitelists, IPWhitelists{CIDR: ipNet}) continue } ip := net.ParseIP(ipWhitelist) if ip != nil { lm.ipWhitelists = append(lm.ipWhitelists, IPWhitelists{IP: ip}) continue } } } // 检查 IP 是否在白名单中 func (lm *LockManager) IsWhitelisted(ip string) bool { clientIP := net.ParseIP(ip) if clientIP == nil { return false } for _, ipWhitelist := range lm.ipWhitelists { if ipWhitelist.CIDR != nil && ipWhitelist.CIDR.Contains(clientIP) { return true } if ipWhitelist.IP != nil && ipWhitelist.IP.Equal(clientIP) { return true } } return false } func (lm *LockManager) StartCleanupTicker() { lm.cleanupTicker = time.NewTicker(5 * time.Minute) go func() { for range lm.cleanupTicker.C { lm.CleanupExpiredLocks() } }() } // 定期清理过期的锁定 func (lm *LockManager) CleanupExpiredLocks() { now := time.Now() lm.mu.Lock() defer lm.mu.Unlock() for ip, state := range lm.ipLocks { if now.Sub(state.LastAttempt) > time.Duration(base.Cfg.GlobalLockStateExpirationTime)*time.Second { delete(lm.ipLocks, ip) } } for user, state := range lm.userLocks { if now.Sub(state.LastAttempt) > time.Duration(base.Cfg.GlobalLockStateExpirationTime)*time.Second { delete(lm.userLocks, user) } } for user, ipMap := range lm.ipUserLocks { for ip, state := range ipMap { if now.Sub(state.LastAttempt) > time.Duration(base.Cfg.GlobalLockStateExpirationTime)*time.Second { delete(ipMap, ip) if len(ipMap) == 0 { delete(lm.ipUserLocks, user) } } } } } // 检查全局 IP 锁定 func (lm *LockManager) CheckGlobalIPLock(ip string, now time.Time) bool { lm.mu.Lock() defer lm.mu.Unlock() state, exists := lm.ipLocks[ip] if !exists { return false } return lm.CheckLockState(state, now, base.Cfg.GlobalIPBanResetTime) } // 检查全局用户锁定 func (lm *LockManager) CheckGlobalUserLock(username string, now time.Time) bool { // 我也不知道为什么cisco anyconnect每次连接会先传一个空用户请求···· if username == "" { return false } lm.mu.Lock() defer lm.mu.Unlock() state, exists := lm.userLocks[username] if !exists { return false } return lm.CheckLockState(state, now, base.Cfg.GlobalUserBanResetTime) } // 检查单个用户的 IP 锁定 func (lm *LockManager) CheckUserIPLock(username, ip string, now time.Time) bool { // 我也不知道为什么cisco anyconnect每次连接会先传一个空用户请求···· if username == "" { return false } lm.mu.Lock() defer lm.mu.Unlock() userIPMap, userExists := lm.ipUserLocks[username] if !userExists { return false } state, ipExists := userIPMap[ip] if !ipExists { return false } return lm.CheckLockState(state, now, base.Cfg.BanResetTime) } // 更新全局 IP 锁定状态 func (lm *LockManager) UpdateGlobalIPLock(ip string, now time.Time, success bool) { lm.mu.Lock() defer lm.mu.Unlock() state, exists := lm.ipLocks[ip] if !exists { state = &LockState{} lm.ipLocks[ip] = state } lm.UpdateLockState(state, now, success, base.Cfg.MaxGlobalIPBanCount, base.Cfg.GlobalIPLockTime) } // 更新全局用户锁定状态 func (lm *LockManager) UpdateGlobalUserLock(username string, now time.Time, success bool) { // 我也不知道为什么cisco anyconnect每次连接会先传一个空用户请求···· if username == "" { return } lm.mu.Lock() defer lm.mu.Unlock() state, exists := lm.userLocks[username] if !exists { state = &LockState{} lm.userLocks[username] = state } lm.UpdateLockState(state, now, success, base.Cfg.MaxGlobalUserBanCount, base.Cfg.GlobalUserLockTime) } // 更新单个用户的 IP 锁定状态 func (lm *LockManager) UpdateUserIPLock(username, ip string, now time.Time, success bool) { // 我也不知道为什么cisco anyconnect每次连接会先传一个空用户请求···· if username == "" { return } lm.mu.Lock() defer lm.mu.Unlock() userIPMap, userExists := lm.ipUserLocks[username] if !userExists { userIPMap = make(map[string]*LockState) lm.ipUserLocks[username] = userIPMap } state, ipExists := userIPMap[ip] if !ipExists { state = &LockState{} userIPMap[ip] = state } lm.UpdateLockState(state, now, success, base.Cfg.MaxBanCount, base.Cfg.LockTime) } // 更新锁定状态 func (lm *LockManager) UpdateLockState(state *LockState, now time.Time, success bool, maxBanCount, lockTime int) { if success { lm.Unlock(state) // 成功登录后解锁 } else { state.FailureCount++ if state.FailureCount >= maxBanCount { state.LockTime = now.Add(time.Duration(lockTime) * time.Second) state.Locked = true // 超过阈值时锁定 } } state.LastAttempt = now } // 检查锁定状态 func (lm *LockManager) CheckLockState(state *LockState, now time.Time, resetTime int) bool { if state == nil || state.LastAttempt.IsZero() { return false } // 如果超过锁定时间,重置锁定状态 if !state.LockTime.IsZero() && now.After(state.LockTime) { lm.Unlock(state) // 锁定期过后解锁 return false } // 如果超过窗口时间,重置失败计数 if now.Sub(state.LastAttempt) > time.Duration(resetTime)*time.Second { state.FailureCount = 0 return false } return state.Locked } // 解锁 func (lm *LockManager) Unlock(state *LockState) { state.FailureCount = 0 state.LockTime = time.Time{} state.Locked = false }