-
-
Notifications
You must be signed in to change notification settings - Fork 420
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #342 from wsczx/dev
1.修复防爆策略用户登录成功后没有重置计数的Bug
- Loading branch information
Showing
9 changed files
with
484 additions
and
466 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,391 @@ | ||
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.