如何在中间件把“没登录”和“坏 session”分开处理?
可以把 LoadCurrentUser 分成两种状态来处理:
- 没有 cookie
这是“匿名访问”,直接放行,不报错。 - 有 cookie,但 session 无效/过期/查不到用户
在 context 里标记出来。
核心思路是:LoadCurrentUser 只负责“尝试加载”,不要默认把所有异常都 401 掉。
可以这样做:
func LoadCurrentUser(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
sessionID, err := c.Cookie("session_id")
if err != nil {
c.Next()
return
}
if sessionID == "" {
c.Next()
return
}
var session models.Session
if err := db.Where("session_id = ?", sessionID).First(&session).Error; err != nil {
c.Set("authError", "invalid_session")
c.Next()
return
}
if session.RevokedAt != nil || (session.ExpiresAt != nil && session.ExpiresAt.Before(time.Now())) {
c.Set("authError", "expired_session")
c.Next()
return
}
var user models.User
if err := db.Preload("Roles.Permissions").First(&user, session.UserId).Error; err != nil {
c.Set("authError", "user_not_found")
c.Next()
return
}
c.Set("currentUser", user)
c.Set("sessionId", sessionID)
c.Next()
}
}然后 RequireAuth 再决定怎么报错:
func RequireAuth() gin.HandlerFunc {
return func(c *gin.Context) {
if user, ok := c.Get("currentUser"); ok && user != nil {
c.Next()
return
}
if authErr, ok := c.Get("authError"); ok {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"msg": authErr})
return
}
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"msg": "unauthorized"})
}
}这样效果就很清楚:
公共接口:
- 没 cookie,正常访问
- 坏 cookie,也正常访问,但你可以选择是否在响应里提示前端重新登录
私有接口:
- 没 cookie,401 unauthorized
- 坏 cookie,401 invalid_session / expired_session
本质其实就是把报错信息放到上下文,然后给需要经过auth中间件的路由去拦截,如果不会经过auth中间件就放行。


