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

Gin框架中gin-contrib/sessions的GetSession返回token为nil问题

问题描述

我基于GIN框架+gin/sessions搭建了后端会话管理模块,前端采用Next.js。登录时通过SetSession函数已成功将token存入会话,但调用/check接口的GetSession获取会话时,token始终返回nil,提示“Session expired”。在SetSession内部检查会话状态正常,然而前端发起请求时却出现该问题,期望返回有效token以允许用户进入/dashboard页面,目前无法定位问题原因。

相关代码

GetSession函数

func GetSession(c *gin.Context) {
    session := sessions.Default(c)
    if value := session.Get("token"); value == nil {
        c.JSON(http.StatusUnauthorized, gin.H{
            "message": "No token present/Session expired",
            "error":   true,
        })
        return
    } else {
        token := value.(string)
        tokenCheck, _ := lib.CheckSecureToken(token)
        if tokenCheck == false {
            c.JSON(http.StatusUnauthorized, gin.H{
                "message": "Invalid token",
                "error":   true,
            })
            return
        }
        c.JSON(http.StatusOK, gin.H{
            "message": "Token present",
            "token":   value.(string),
            "error":   false,
        })
    }
    return
}

SetSession函数

func SetSession(c *gin.Context, token string, status int) {
    session := sessions.Default(c)
    session.Set("token", token)
    err := session.Save()
    if err != nil {
        c.JSON(500, gin.H{
            "message": "Error saving session, user not logged in",
            "error":   true,
            "status":  500,
        })
    }
    c.JSON(status, gin.H{
        "message": "User logged in successfully",
        "token":   token,
        "error":   false,
        "status":  200,
    })
}

SetupRouter函数

func SetupRouter() *gin.Engine {
    r := gin.Default()
    config := cors.DefaultConfig()
    store := cookie.NewStore([]byte("")) //Secret is set
    store.Options(sessions.Options{
        MaxAge: 60 * 60 * 24,
    })
    r.Use(sessions.Sessions("usersession", store))
    config.AllowOrigins = []string{"http://localhost:3000"}
    r.Use(cors.New(config))
    r.GET("/", home)

    //Auth routes
    authGroup := r.Group("/api/v1/auth")
    //authGroup.POST("/logout", logoutUser)
    //authGroup.POST("/refresh", refreshUser)
    //authGroup.POST("/forgot", forgotPassword)
    //authGroup.POST("/reset", resetPassword)
    authGroup.POST("/login", loginUser)
    authGroup.POST("/signup", createUser)
    authGroup.GET("/check", handler.GetSession)
}

loginUser函数

func loginUser(c *gin.Context) {
    var user database.User
    err := c.BindJSON(&user)
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error(), "message": "User not found"})
        return
    }
    db, errS := database.LoginUser(&user)
    if errS != nil {
        c.JSON(http.StatusBadRequest, gin.H{"type": "Authentication Error", "message": "Invalid email or password", "status": "400"})
        return
    }
    token := lib.GenerateSecureToken(user.Email)
    if db.IsAdmin {
        adminToken := lib.GenerateAdminSecureToken(user.Email)
        cookieAdmin := adminToken
        handler.SetAdminSession(c, cookieAdmin, 200)
    }
    handler.SetSession(c, token, 200)
}
排查与解决方案

1. 补全CORS凭证支持

当前CORS配置未允许携带凭证(Cookie),前端请求时无法传递后端设置的Session Cookie。修改SetupRouter中的CORS配置:

config := cors.DefaultConfig()
config.AllowOrigins = []string{"http://localhost:3000"}
config.AllowCredentials = true // 开启凭证支持
config.AllowHeaders = append(config.AllowHeaders, "Authorization") // 可选,确保头部兼容
r.Use(cors.New(config))

同时前端请求需添加credentials: 'include',比如Next.js中使用fetch:

fetch('http://localhost:8080/api/v1/auth/check', {
  credentials: 'include'
})

2. 设置有效Session密钥

代码中cookie.NewStore([]byte(""))的密钥为空(即使注释说已设置),会导致Session签名无效,浏览器无法正确解析Cookie。必须设置非空的随机密钥:

// 建议用openssl rand -hex 32生成随机字符串
store := cookie.NewStore([]byte("your-32-or-64-character-random-secret"))

3. 完善Cookie选项配置

补充Cookie的路径、安全属性,避免浏览器拦截:

store.Options(sessions.Options{
    MaxAge:   60 * 60 * 24,
    Path:     "/", // 确保Cookie在全站生效
    HttpOnly: true, // 防XSS攻击
    SameSite: http.SameSiteLaxMode, // 适配跨域场景
})

4. 修正SetSession的错误处理逻辑

当前SetSession中即使Session保存失败,仍会返回成功响应,导致前端误判登录状态。修改如下:

func SetSession(c *gin.Context, token string, status int) {
    session := sessions.Default(c)
    session.Set("token", token)
    err := session.Save()
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{
            "message": "Error saving session, user not logged in",
            "error":   true,
            "status":  http.StatusInternalServerError,
        })
        return // 保存失败直接返回,不执行后续成功响应
    }
    c.JSON(status, gin.H{
        "message": "User logged in successfully",
        "token":   token,
        "error":   false,
        "status":  http.StatusOK,
    })
}

5. 检查Session名称冲突

loginUser中如果用户是管理员,会同时调用SetAdminSession和SetSession,需确保两个Session使用不同的名称,避免互相覆盖token。比如SetAdminSession应使用独立的Session名称(如adminsession)。

6. 验证源地址一致性

确保前端请求的后端地址与AllowOrigins配置完全匹配,比如前端用http://localhost:3000,后端不能配置成http://127.0.0.1:3000,否则会被浏览器视为不同源,拒绝传递Cookie。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 04:10:35