You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Golang Goth会话丢失问题:认证后访问根路径无用户信息

问题:Goth包认证后根路径无法读取会话信息

我正在使用Goth包按照示例代码测试,认证功能正常,回调后能看到用户信息,但访问/路径时会话为空,无法查看用户信息。请问我遗漏了什么?

package main

import (
    "context"
    "fmt"
    "github.com/gorilla/sessions"
    "html/template"
    "log"
    "net/http"
    "os"
    "sort"

    "github.com/gorilla/pat"
    "github.com/joho/godotenv"
    "github.com/markbates/goth"
    "github.com/markbates/goth/gothic"
    "github.com/markbates/goth/providers/azuread"
)

func main() {

    err := godotenv.Load()
    if err != nil {
        log.Fatal("Error loading file")
    }
    store := sessions.NewFilesystemStore("", []byte(os.Getenv("SESSION_KEY")))
    store.Options.Path = "/"
    store.Options.HttpOnly = true
    store.Options.Secure = false
    store.MaxLength(6000)

    gothic.Store = store
    goth.UseProviders(
        azuread.New(os.Getenv("AZUREAD_KEY"), os.Getenv("AZUREAD_SECRET"), "http://localhost:8080/auth/azuread/callback", azuread.ProviderOpts{TenantID: os.Getenv("AZUREAD_TENANT_ID")}),
    )

    m := map[string]string{
        "azuread": "Azure AD",
    }
    var keys []string
    for k := range m {
        keys = append(keys, k)
    }
    sort.Strings(keys)

    providerIndex := &ProviderIndex{Providers: keys, ProvidersMap: m}

    p := pat.New()
    p.Get("/auth/{provider}/callback", func(res http.ResponseWriter, req *http.Request) {

        user, err := gothic.CompleteUserAuth(res, req)
        if err != nil {
            fmt.Fprintln(res, err)
            return
        }
        t, _ := template.New("foo").Parse(userTemplate)
        t.Execute(res, user)
    })

    p.Get("/logout/{provider}", func(res http.ResponseWriter, req *http.Request) {
        gothic.Logout(res, req)
        res.Header().Set("Location", "/")
        res.WriteHeader(http.StatusTemporaryRedirect)
    })

    p.Get("/auth/{provider}", func(res http.ResponseWriter, req *http.Request) {
        // try to get the user without re-authenticating
        if gothUser, err := gothic.CompleteUserAuth(res, req); err == nil {
            t, _ := template.New("foo").Parse(userTemplate)
            t.Execute(res, gothUser)
        } else {
            gothic.BeginAuthHandler(res, req)
        }
    })

    p.Get("/", func(res http.ResponseWriter, req *http.Request) {
        req = req.WithContext(context.WithValue(req.Context(), gothic.ProviderParamKey, "azuread"))
        user, err := gothic.CompleteUserAuth(res, req)
        if err != nil {
            t, _ := template.New("foo").Parse(indexTemplate)
            t.Execute(res, providerIndex)
        } else {
            t, _ := template.New("foo").Parse(userTemplate)
            t.Execute(res, user)
        }
    })

    log.Println("listening on localhost:8080")
    log.Fatal(http.ListenAndServe(":8080", p))
}

type ProviderIndex struct {
    Providers    []string
    ProvidersMap map[string]string
}

var indexTemplate = `{{range $key,$value:=.Providers}}
    <p><a href="/auth/{{$value}}">Log in with {{index $.ProvidersMap $value}}</a></p>
{{end}}`

var userTemplate = `
<p><a href="/logout/{{.Provider}}">logout</a></p>
<p>Name: {{.Name}} [{{.LastName}}, {{.FirstName}}]</p>
<p>Email: {{.Email}}</p>
<p>NickName: {{.NickName}}</p>
<p>Location: {{.Location}}</p>
<p>AvatarURL: {{.AvatarURL}} <img src="{{.AvatarURL}}"></p>
<p>Description: {{.Description}}</p>
<p>UserID: {{.UserID}}</p>
<p>AccessToken: {{.AccessToken}}</p>
<p>ExpiresAt: {{.ExpiresAt}}</p>
<p>RefreshToken: {{.RefreshToken}}</p>
`

问题原因

gothic.CompleteUserAuth的设计逻辑是:仅在回调阶段调用一次,用于从会话中取出临时认证数据、交换为用户信息,之后会自动清除会话中的临时认证数据。你在根路径再次调用该方法时,会话已经没有可用的认证数据,自然返回错误。

解决方案

需要在回调阶段手动将用户信息存入会话,后续从自定义会话中读取用户数据,而非重复调用CompleteUserAuth。

修改后的关键代码

  1. 回调函数:保存用户信息到会话
p.Get("/auth/{provider}/callback", func(res http.ResponseWriter, req *http.Request) {
    user, err := gothic.CompleteUserAuth(res, req)
    if err != nil {
        fmt.Fprintln(res, err)
        return
    }
    // 将用户信息存入自定义会话
    session, _ := store.Get(req, "goth-user-session")
    session.Values["current_user"] = user
    session.Save(req, res)
    // 重定向到根路径
    res.Header().Set("Location", "/")
    res.WriteHeader(http.StatusTemporaryRedirect)
})
  1. 根路径:从会话读取用户信息
p.Get("/", func(res http.ResponseWriter, req *http.Request) {
    session, err := store.Get(req, "goth-user-session")
    if err != nil || session.Values["current_user"] == nil {
        t, _ := template.New("foo").Parse(indexTemplate)
        t.Execute(res, providerIndex)
    } else {
        user := session.Values["current_user"].(goth.User)
        t, _ := template.New("foo").Parse(userTemplate)
        t.Execute(res, user)
    }
})
  1. 登出函数:清空自定义会话
p.Get("/logout/{provider}", func(res http.ResponseWriter, req *http.Request) {
    gothic.Logout(res, req)
    // 清空自定义会话中的用户信息
    session, _ := store.Get(req, "goth-user-session")
    delete(session.Values, "current_user")
    session.Save(req, res)
    res.Header().Set("Location", "/")
    res.WriteHeader(http.StatusTemporaryRedirect)
})

内容的提问来源于stack exchange,提问作者trookam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 13:24:53