Scert/backend/internal/httpapi/auth_admission.go

68 lines
1.8 KiB
Go

package httpapi
import (
"net/http"
"strings"
"time"
)
const (
loginConcurrentLimit = 8
loginSourceConcurrentLimit = 2
loginSourceRequestLimit = 30
)
type loginSource struct {
WindowStart time.Time
Requests int
InFlight int
}
// 在密码计算和数据库查询之前预约容量,不建立无限等待队列。
// 来源只取真实 TCP 对端;不信任任意转发头。代理部署应另外配置边缘限流。
func (s *Server) reserveLogin(request *http.Request, key string) bool {
sourceKey := strings.SplitN(loginAttemptKey(request, ""), "|", 2)[0]
now := s.now()
s.loginMu.Lock()
defer s.loginMu.Unlock()
for name, source := range s.loginSources {
if source.InFlight == 0 && now.Sub(source.WindowStart) >= time.Minute {
delete(s.loginSources, name)
}
}
source, exists := s.loginSources[sourceKey]
attempt := s.loginAttempts[key]
if attempt.BlockedTill.After(now) || attempt.Failures+s.loginPending[key] >= loginFailureLimit ||
s.loginInFlight >= loginConcurrentLimit || source.InFlight >= loginSourceConcurrentLimit ||
(!exists && len(s.loginSources) >= authStateCapacity) {
return false
}
if source.WindowStart.IsZero() || now.Sub(source.WindowStart) >= time.Minute {
source.WindowStart = now
source.Requests = 0
}
if source.Requests >= loginSourceRequestLimit {
return false
}
source.Requests++
source.InFlight++
s.loginSources[sourceKey] = source
s.loginInFlight++
s.loginPending[key]++
return true
}
func (s *Server) releaseLogin(request *http.Request, key string) {
sourceKey := strings.SplitN(loginAttemptKey(request, ""), "|", 2)[0]
s.loginMu.Lock()
defer s.loginMu.Unlock()
source := s.loginSources[sourceKey]
source.InFlight--
s.loginSources[sourceKey] = source
s.loginInFlight--
s.loginPending[key]--
if s.loginPending[key] == 0 {
delete(s.loginPending, key)
}
}