Golang登录和登出功能问题排查

Golang登录和登出功能问题排查 大家好,我是Go语言的新手,正在开发一个简单的登录/登出流程。我能够成功登录和登出用户,但当我第二次登出用户并重新登录后,这次却无法登出。第一次用户登录时,在/test页面上能显示用户名,首次登出时也能重置cookie。但当我再次登录并登出时,登录验证检查的cookie却没有被重置。以下是我的代码,请帮忙看看。

package main

import (
	"log"
	"net/http"
	"text/template"

	"github.com/gorilla/sessions"

	"github.com/gorilla/mux"
)

var t *template.Template
var store *sessions.CookieStore

func init() {
	t = template.Must(template.ParseGlob("templates/*.html"))
	store = sessions.NewCookieStore([]byte("t0p-s3cr3t"))
	store.Options.HttpOnly = true
}

func main() {
	r := mux.NewRouter()
	r.HandleFunc("/hello", helloHandler).Methods("GET")
	r.HandleFunc("/forbidden", forbiddenHandler).Methods("GET")

	r.HandleFunc("/login", loginGetHandler).Methods("GET")
	r.HandleFunc("/login", loginPostHandler).Methods("POST")

	r.HandleFunc("/test", testHandler).Methods("GET")
	r.HandleFunc("/logout", logoutHandler)

	r.HandleFunc("/", handler).Methods("GET")

	fs := http.FileServer(http.Dir("./static/"))
	r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", fs))

	http.Handle("/", r)
	http.ListenAndServe(":8080", nil)
}

func loginGetHandler(w http.ResponseWriter, r *http.Request) {
	page := struct {
		PageTitle string
	}{
		PageTitle: "Login Here",
	}
	t.ExecuteTemplate(w, "login.html", page)
}

func loginPostHandler(w http.ResponseWriter, r *http.Request) {
	r.ParseForm()
	username := r.PostForm.Get("username")
	session, _ := store.Get(r, "session")
	session.Values["username"] = username
	session.Save(r, w)
	http.Redirect(w, r, "/test", 301)
}
func handler(w http.ResponseWriter, r *http.Request) {
	page := struct {
		PageTitle string
		Name      string
		Age       int
	}{
		PageTitle: "Welcome to new world of golang",
		Name:      "Deepak Singh Kushwah",
		Age:       36,
	}
	t.ExecuteTemplate(w, "index.html", page)
}
func helloHandler(w http.ResponseWriter, r *http.Request) {
	page := struct {
		PageTitle string
		Name      string
		Age       int
	}{
		PageTitle: "Hello World Handler",
		Name:      "Hello World",
		Age:       40,
	}
	t.ExecuteTemplate(w, "index.html", page)
}

func forbiddenHandler(w http.ResponseWriter, r *http.Request) {
	page := struct {
		PageTitle string
		Name      string
	}{
		PageTitle: "403",
		Name:      "403",
	}
	t.ExecuteTemplate(w, "forbidden.html", page)
}

func testHandler(w http.ResponseWriter, r *http.Request) {
	session, _ := store.Get(r, "session")
	untyped, ok := session.Values["username"]
	if !ok {
		http.Redirect(w, r, "/forbidden", 301)
		return
	}
	username, ok := untyped.(string)
	if !ok || username == "" {
		http.Redirect(w, r, "/forbidden", 301)
		return
	}
	page := struct {
		PageTitle string
		Name      string
	}{
		PageTitle: "TEST Handler",
		Name:      username,
	}
	t.ExecuteTemplate(w, "test.html", page)
}

func loginCheck(r *http.Request) bool {
	session, _ := store.Get(r, "session")
	untyped, ok := session.Values["username"]
	if !ok {
		return false
	}
	_, ok = untyped.(string)
	if !ok {
		return false
	}
	return true
}
func logoutHandler(w http.ResponseWriter, r *http.Request) {
	session, err := store.Get(r, "session")
	checkErr(err)
	session.Values["username"] = ""
	err = session.Save(r, w)
	checkErr(err)
	http.Redirect(w, r, "/", 301)
}

func checkErr(e error) {
	if e != nil {
		log.Panic(e)
	}
}

更多关于Golang登录和登出功能问题排查的实战教程也可以访问 https://www.itying.com/category-94-b0.html

7 回复

302 更为便捷。

更多关于Golang登录和登出功能问题排查的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


太好了,你已经成功解决了问题!能否与我们分享一下你的解决方案是什么?

geosoft1:

在代码中可以使用一些代表HTTP状态码的常量作为正则表达式

是的,我使用了302而不是301,问题就解决了。感谢大家的支持。

但是303更好,因为它将所有请求转换为GET,所以如果你从POST、DELETE、UPDATE等操作进行重定向,它会自动转换为GET(这在99%的情况下都是你想要的)。例如,请参阅此解释:https://serverfault.com/questions/391181/examples-of-302-vs-303

引用 deepaksinghkushwah:

checkErr(err) http.Redirect(w, r, "/", 301)

不要使用表示永久移动的301状态码。这会让浏览器认为访问此URL没有必要,因为它已永久移动到根路径。请改用303"查看其他"状态码。

http.Redirect(w, r, "/", 301)

相比直接使用数字代码,您可以使用一些代表HTTP状态码的常量,这些常量已在IANA注册(参见https://golang.org/src/net/http/status.go)。

http.Redirect(w, r, "/", http.StatusSeeOther)

问题出现在你的 logoutHandler 函数中。当用户登出时,你只是将 session.Values["username"] 设置为空字符串,但没有真正删除会话或将其标记为过期。这导致第二次登出时,会话仍然存在,只是用户名为空,但 loginCheck 函数仍然返回 true

以下是修复后的代码:

func logoutHandler(w http.ResponseWriter, r *http.Request) {
    session, err := store.Get(r, "session")
    checkErr(err)
    
    // 完全删除会话
    session.Options.MaxAge = -1
    err = session.Save(r, w)
    checkErr(err)
    
    http.Redirect(w, r, "/", http.StatusSeeOther)
}

同时,建议更新 loginCheck 函数以正确处理空字符串的情况:

func loginCheck(r *http.Request) bool {
    session, _ := store.Get(r, "session")
    untyped, ok := session.Values["username"]
    if !ok {
        return false
    }
    username, ok := untyped.(string)
    if !ok || username == "" {
        return false
    }
    return true
}

另外,建议将所有重定向的状态码从 301 改为 303307,因为 301 是永久重定向,可能会被浏览器缓存:

// 在所有重定向的地方使用
http.Redirect(w, r, "/", http.StatusSeeOther) // 303

主要修改说明:

  • logoutHandler 中设置 session.Options.MaxAge = -1 来使会话立即过期
  • loginCheck 中明确检查用户名是否为空字符串
  • 使用更合适的重定向状态码

这样修改后,每次登出都会完全清除会话,确保后续的登录验证能够正常工作。

回到顶部