package middleware import ( "crypto/rand" "encoding/hex" "net/http" "sync" "time" "github.com/gin-gonic/gin" ) // ---- 服务端会话存储(内存) ---- // 用随机 token 代替之前的固定字符串 cookie,登出/过期即失效。 var ( sessions = make(map[string]time.Time) // token -> 过期时间 sessionsMu sync.RWMutex ) const ( CookieName = "admin_token" SessionTTL = 7 * 24 * time.Hour // 会话有效期 7 天 CookieMaxAge = 7 * 24 * 3600 // cookie 有效期(秒) sessionCleanT = 10 // 清理过期会话的间隔(分钟) ) // NewSessionToken 生成一个新的会话 token 并注册 func NewSessionToken() (string, time.Time) { b := make([]byte, 32) if _, err := rand.Read(b); err != nil { // 兜底:用时间戳+纳秒(理论上不会发生) b = []byte(time.Now().Format("20060102150405.000000000")) } token := hex.EncodeToString(b) exp := time.Now().Add(SessionTTL) sessionsMu.Lock() sessions[token] = exp sessionsMu.Unlock() go cleanExpiredSessions() return token, exp } // RevokeSession 登出时删除会话 func RevokeSession(token string) { sessionsMu.Lock() delete(sessions, token) sessionsMu.Unlock() } // IsValidSession 校验 token 是否有效且未过期 func IsValidSession(token string) bool { if token == "" { return false } sessionsMu.RLock() exp, ok := sessions[token] sessionsMu.RUnlock() if !ok { return false } if time.Now().After(exp) { RevokeSession(token) return false } return true } // cleanExpiredSessions 定期清理过期会话,防止内存泄漏 func cleanExpiredSessions() { sessionsMu.Lock() defer sessionsMu.Unlock() for token, exp := range sessions { if time.Now().After(exp) { delete(sessions, token) } } } // AuthRequired 管理接口认证中间件 func AuthRequired() gin.HandlerFunc { return func(c *gin.Context) { token, err := c.Cookie(CookieName) if err != nil || !IsValidSession(token) { c.JSON(http.StatusUnauthorized, gin.H{"code": 401, "message": "请先登录"}) c.Abort() return } c.Next() } } // SetAuthCookie 设置认证 cookie(SameSite=Lax 防 CSRF;https 下应启用 Secure) func SetAuthCookie(c *gin.Context, token string, secure bool) { c.SetSameSite(http.SameSiteLaxMode) c.SetCookie(CookieName, token, CookieMaxAge, "/", "", secure, true) } // ClearAuthCookie 清除认证 cookie func ClearAuthCookie(c *gin.Context) { c.SetSameSite(http.SameSiteLaxMode) c.SetCookie(CookieName, "", -1, "/", "", false, true) } // GetAuthToken 从请求读取 token func GetAuthToken(c *gin.Context) string { token, _ := c.Cookie(CookieName) return token }