Fix Services & Update the middleware && others

This commit is contained in:
XOF
2025-11-24 04:48:07 +08:00
parent 3a95a07e8a
commit f2706d6fc8
37 changed files with 4458 additions and 1166 deletions

View File

@@ -1,23 +1,151 @@
// Filename: internal/middleware/web.go
package middleware
import (
"crypto/sha256"
"encoding/hex"
"gemini-balancer/internal/service"
"log"
"net/http"
"os"
"strings"
"sync"
"time"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
)
const (
AdminSessionCookie = "gemini_admin_session"
SessionMaxAge = 3600 * 24 * 7 // 7天
CacheTTL = 5 * time.Minute
CleanupInterval = 10 * time.Minute // 降低清理频率
SessionRefreshTime = 30 * time.Minute
)
// ==================== 缓存层 ====================
type authCacheEntry struct {
Token interface{}
ExpiresAt time.Time
}
type authCache struct {
mu sync.RWMutex
cache map[string]*authCacheEntry
ttl time.Duration
}
var webAuthCache = newAuthCache(CacheTTL)
func newAuthCache(ttl time.Duration) *authCache {
c := &authCache{
cache: make(map[string]*authCacheEntry),
ttl: ttl,
}
go c.cleanupLoop()
return c
}
func (c *authCache) get(key string) (interface{}, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
entry, exists := c.cache[key]
if !exists || time.Now().After(entry.ExpiresAt) {
return nil, false
}
return entry.Token, true
}
func (c *authCache) set(key string, token interface{}) {
c.mu.Lock()
defer c.mu.Unlock()
c.cache[key] = &authCacheEntry{
Token: token,
ExpiresAt: time.Now().Add(c.ttl),
}
}
func (c *authCache) delete(key string) {
c.mu.Lock()
defer c.mu.Unlock()
delete(c.cache, key)
}
func (c *authCache) cleanupLoop() {
ticker := time.NewTicker(CleanupInterval)
defer ticker.Stop()
for range ticker.C {
c.cleanup()
}
}
func (c *authCache) cleanup() {
c.mu.Lock()
defer c.mu.Unlock()
now := time.Now()
count := 0
for key, entry := range c.cache {
if now.After(entry.ExpiresAt) {
delete(c.cache, key)
count++
}
}
if count > 0 {
logrus.Debugf("[AuthCache] Cleaned up %d expired entries", count)
}
}
// ==================== 会话刷新缓存 ====================
var sessionRefreshCache = struct {
sync.RWMutex
timestamps map[string]time.Time
}{
timestamps: make(map[string]time.Time),
}
// 定期清理刷新时间戳
func init() {
go func() {
ticker := time.NewTicker(1 * time.Hour)
defer ticker.Stop()
for range ticker.C {
sessionRefreshCache.Lock()
now := time.Now()
for key, ts := range sessionRefreshCache.timestamps {
if now.Sub(ts) > 2*time.Hour {
delete(sessionRefreshCache.timestamps, key)
}
}
sessionRefreshCache.Unlock()
}
}()
}
// ==================== Cookie 操作 ====================
func SetAdminSessionCookie(c *gin.Context, adminToken string) {
c.SetCookie(AdminSessionCookie, adminToken, 3600*24*7, "/", "", false, true)
secure := c.Request.TLS != nil || c.GetHeader("X-Forwarded-Proto") == "https"
c.SetSameSite(http.SameSiteStrictMode)
c.SetCookie(AdminSessionCookie, adminToken, SessionMaxAge, "/", "", secure, true)
}
func SetAdminSessionCookieWithAge(c *gin.Context, adminToken string, maxAge int) {
secure := c.Request.TLS != nil || c.GetHeader("X-Forwarded-Proto") == "https"
c.SetSameSite(http.SameSiteStrictMode)
c.SetCookie(AdminSessionCookie, adminToken, maxAge, "/", "", secure, true)
}
func ClearAdminSessionCookie(c *gin.Context) {
c.SetSameSite(http.SameSiteStrictMode)
c.SetCookie(AdminSessionCookie, "", -1, "/", "", false, true)
}
@@ -29,26 +157,258 @@ func ExtractTokenFromCookie(c *gin.Context) string {
return cookie
}
// ==================== 认证中间件 ====================
func WebAdminAuthMiddleware(authService *service.SecurityService) gin.HandlerFunc {
logger := logrus.New()
logger.SetLevel(getLogLevel())
return func(c *gin.Context) {
cookie := ExtractTokenFromCookie(c)
log.Printf("[WebAuth_Guard] Intercepting request for: %s", c.Request.URL.Path)
log.Printf("[WebAuth_Guard] Found session cookie value: '%s'", cookie)
authToken, err := authService.AuthenticateToken(cookie)
if err != nil {
log.Printf("[WebAuth_Guard] FATAL: AuthenticateToken FAILED. Error: %v. Redirecting to /login.", err)
} else if !authToken.IsAdmin {
log.Printf("[WebAuth_Guard] FATAL: Token validated, but IsAdmin is FALSE. Redirecting to /login.")
} else {
log.Printf("[WebAuth_Guard] SUCCESS: Token validated and IsAdmin is TRUE. Allowing access.")
}
if err != nil || !authToken.IsAdmin {
if cookie == "" {
logger.Debug("[WebAuth] No session cookie found")
ClearAdminSessionCookie(c)
c.Redirect(http.StatusFound, "/login")
c.Abort()
redirectToLogin(c)
return
}
cacheKey := hashToken(cookie)
if cachedToken, found := webAuthCache.get(cacheKey); found {
logger.Debug("[WebAuth] Using cached token")
c.Set("adminUser", cachedToken)
refreshSessionIfNeeded(c, cookie)
c.Next()
return
}
logger.Debug("[WebAuth] Cache miss, authenticating...")
authToken, err := authService.AuthenticateToken(cookie)
if err != nil {
logger.WithError(err).Warn("[WebAuth] Authentication failed")
ClearAdminSessionCookie(c)
webAuthCache.delete(cacheKey)
redirectToLogin(c)
return
}
if !authToken.IsAdmin {
logger.Warn("[WebAuth] User is not admin")
ClearAdminSessionCookie(c)
webAuthCache.delete(cacheKey)
redirectToLogin(c)
return
}
webAuthCache.set(cacheKey, authToken)
logger.Debug("[WebAuth] Authentication success, token cached")
c.Set("adminUser", authToken)
refreshSessionIfNeeded(c, cookie)
c.Next()
}
}
func WebAdminAuthMiddlewareWithLogger(authService *service.SecurityService, logger *logrus.Logger) gin.HandlerFunc {
return func(c *gin.Context) {
cookie := ExtractTokenFromCookie(c)
if cookie == "" {
logger.Debug("No session cookie found")
ClearAdminSessionCookie(c)
redirectToLogin(c)
return
}
cacheKey := hashToken(cookie)
if cachedToken, found := webAuthCache.get(cacheKey); found {
c.Set("adminUser", cachedToken)
refreshSessionIfNeeded(c, cookie)
c.Next()
return
}
authToken, err := authService.AuthenticateToken(cookie)
if err != nil {
logger.WithError(err).Warn("Token authentication failed")
ClearAdminSessionCookie(c)
webAuthCache.delete(cacheKey)
redirectToLogin(c)
return
}
if !authToken.IsAdmin {
logger.Warn("Token valid but user is not admin")
ClearAdminSessionCookie(c)
webAuthCache.delete(cacheKey)
redirectToLogin(c)
return
}
webAuthCache.set(cacheKey, authToken)
c.Set("adminUser", authToken)
refreshSessionIfNeeded(c, cookie)
c.Next()
}
}
// ==================== 辅助函数 ====================
func hashToken(token string) string {
h := sha256.Sum256([]byte(token))
return hex.EncodeToString(h[:])
}
func redirectToLogin(c *gin.Context) {
if isAjaxRequest(c) {
c.JSON(http.StatusUnauthorized, gin.H{
"error": "Session expired",
"code": "AUTH_REQUIRED",
})
c.Abort()
return
}
originalPath := c.Request.URL.Path
if originalPath != "/" && originalPath != "/login" {
c.Redirect(http.StatusFound, "/login?redirect="+originalPath)
} else {
c.Redirect(http.StatusFound, "/login")
}
c.Abort()
}
func isAjaxRequest(c *gin.Context) bool {
// 检查 Content-Type
contentType := c.GetHeader("Content-Type")
if strings.Contains(contentType, "application/json") {
return true
}
// 检查 Accept优先检查 JSON
accept := c.GetHeader("Accept")
if strings.Contains(accept, "application/json") &&
!strings.Contains(accept, "text/html") {
return true
}
// 兼容旧版 XMLHttpRequest
return c.GetHeader("X-Requested-With") == "XMLHttpRequest"
}
func refreshSessionIfNeeded(c *gin.Context, token string) {
tokenHash := hashToken(token)
sessionRefreshCache.RLock()
lastRefresh, exists := sessionRefreshCache.timestamps[tokenHash]
sessionRefreshCache.RUnlock()
if !exists || time.Since(lastRefresh) > SessionRefreshTime {
SetAdminSessionCookie(c, token)
sessionRefreshCache.Lock()
sessionRefreshCache.timestamps[tokenHash] = time.Now()
sessionRefreshCache.Unlock()
}
}
func getLogLevel() logrus.Level {
level := os.Getenv("LOG_LEVEL")
switch strings.ToLower(level) {
case "debug":
return logrus.DebugLevel
case "warn":
return logrus.WarnLevel
case "error":
return logrus.ErrorLevel
default:
return logrus.InfoLevel
}
}
// ==================== 工具函数 ====================
func GetAdminUserFromContext(c *gin.Context) (interface{}, bool) {
return c.Get("adminUser")
}
func InvalidateTokenCache(token string) {
tokenHash := hashToken(token)
webAuthCache.delete(tokenHash)
// 同时清理刷新时间戳
sessionRefreshCache.Lock()
delete(sessionRefreshCache.timestamps, tokenHash)
sessionRefreshCache.Unlock()
}
func ClearAllAuthCache() {
webAuthCache.mu.Lock()
webAuthCache.cache = make(map[string]*authCacheEntry)
webAuthCache.mu.Unlock()
sessionRefreshCache.Lock()
sessionRefreshCache.timestamps = make(map[string]time.Time)
sessionRefreshCache.Unlock()
}
// ==================== 调试工具 ====================
type SessionInfo struct {
HasCookie bool `json:"has_cookie"`
IsValid bool `json:"is_valid"`
IsAdmin bool `json:"is_admin"`
IsCached bool `json:"is_cached"`
LastActivity string `json:"last_activity"`
}
func GetSessionInfo(c *gin.Context, authService *service.SecurityService) SessionInfo {
info := SessionInfo{
HasCookie: false,
IsValid: false,
IsAdmin: false,
IsCached: false,
LastActivity: time.Now().Format(time.RFC3339),
}
cookie := ExtractTokenFromCookie(c)
if cookie == "" {
return info
}
info.HasCookie = true
cacheKey := hashToken(cookie)
if _, found := webAuthCache.get(cacheKey); found {
info.IsCached = true
}
authToken, err := authService.AuthenticateToken(cookie)
if err != nil {
return info
}
info.IsValid = true
info.IsAdmin = authToken.IsAdmin
return info
}
func GetCacheStats() map[string]interface{} {
webAuthCache.mu.RLock()
cacheSize := len(webAuthCache.cache)
webAuthCache.mu.RUnlock()
sessionRefreshCache.RLock()
refreshSize := len(sessionRefreshCache.timestamps)
sessionRefreshCache.RUnlock()
return map[string]interface{}{
"auth_cache_entries": cacheSize,
"refresh_cache_entries": refreshSize,
"ttl_seconds": int(webAuthCache.ttl.Seconds()),
"cleanup_interval": int(CleanupInterval.Seconds()),
"session_refresh_time": int(SessionRefreshTime.Seconds()),
}
}