// auth/middleware.go package auth import ( "crypto/subtle" "html/template" "net/http" "time" ) type AuthMiddleware struct { username string password string sessions *SessionManager templates *template.Template } func NewAuthMiddleware(username, password string, sessions *SessionManager, templates *template.Template) *AuthMiddleware { return &AuthMiddleware{ username: username, password: password, sessions: sessions, templates: templates, } } func (a *AuthMiddleware) Login(w http.ResponseWriter, r *http.Request) { if r.Method == "GET" { showError := r.URL.Query().Get("error") == "1" a.serveLoginPage(w, showError) return } if r.Method != "POST" { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } username := r.FormValue("username") password := r.FormValue("password") // 使用常量时间比较防止时序攻击 usernameMatch := subtle.ConstantTimeCompare([]byte(username), []byte(a.username)) == 1 passwordMatch := subtle.ConstantTimeCompare([]byte(password), []byte(a.password)) == 1 if usernameMatch && passwordMatch { sessionID := a.sessions.Create(30 * time.Minute) http.SetCookie(w, &http.Cookie{ Name: "session_id", Value: sessionID, Path: "/", HttpOnly: true, Secure: r.TLS != nil, // 只在 HTTPS 时设置 Secure SameSite: http.SameSiteStrictMode, MaxAge: 1800, }) http.Redirect(w, r, "/", http.StatusSeeOther) return } // 登录失败,延迟响应防止暴力破解 time.Sleep(2 * time.Second) http.Redirect(w, r, "/login?error=1", http.StatusSeeOther) } func (a *AuthMiddleware) Logout(w http.ResponseWriter, r *http.Request) { cookie, err := r.Cookie("session_id") if err == nil { a.sessions.Delete(cookie.Value) } http.SetCookie(w, &http.Cookie{ Name: "session_id", Value: "", Path: "/", HttpOnly: true, Secure: r.TLS != nil, MaxAge: -1, }) http.Redirect(w, r, "/login", http.StatusSeeOther) } func (a *AuthMiddleware) Require(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { cookie, err := r.Cookie("session_id") if err != nil || !a.sessions.Valid(cookie.Value) { http.Redirect(w, r, "/login", http.StatusFound) return } next.ServeHTTP(w, r) }) } func (a *AuthMiddleware) serveLoginPage(w http.ResponseWriter, showError bool) { data := struct { Error bool }{ Error: showError, } w.Header().Set("Content-Type", "text/html; charset=utf-8") if err := a.templates.ExecuteTemplate(w, "login.html", data); err != nil { http.Error(w, "Internal server error", http.StatusInternalServerError) } }