Cookie与Session #

一、Cookie操作 #

1.1 设置Cookie #

go
func main() {
    r := gin.Default()
    
    r.GET("/set-cookie", func(c *gin.Context) {
        // 基本设置
        c.SetCookie("user", "alice", 3600, "/", "localhost", false, true)
        
        // 参数说明:
        // name: Cookie名称
        // value: Cookie值
        // maxAge: 过期时间(秒),-1表示会话Cookie,0表示删除
        // path: 有效路径
        // domain: 域名
        // secure: 是否只在HTTPS下传输
        // httpOnly: 是否禁止JavaScript访问
        
        c.String(200, "Cookie已设置")
    })
    
    r.Run()
}

1.2 获取Cookie #

go
func main() {
    r := gin.Default()
    
    r.GET("/get-cookie", func(c *gin.Context) {
        // 获取Cookie
        user, err := c.Cookie("user")
        if err != nil {
            c.String(200, "Cookie不存在")
            return
        }
        
        c.String(200, "Cookie值: %s", user)
    })
    
    r.Run()
}

1.3 删除Cookie #

go
func main() {
    r := gin.Default()
    
    r.GET("/delete-cookie", func(c *gin.Context) {
        // 设置maxAge为0删除Cookie
        c.SetCookie("user", "", -1, "/", "localhost", false, true)
        
        c.String(200, "Cookie已删除")
    })
    
    r.Run()
}

1.4 Cookie选项 #

go
func main() {
    r := gin.Default()
    
    r.GET("/secure-cookie", func(c *gin.Context) {
        // 安全Cookie设置
        c.SetCookie(
            "session_id",           // 名称
            "abc123",               // 值
            3600,                   // 过期时间
            "/",                    // 路径
            "example.com",          // 域名
            true,                   // Secure - 只在HTTPS传输
            true,                   // HttpOnly - 禁止JS访问
        )
        
        // SameSite设置
        c.Header("Set-Cookie", 
            "session_id=abc123; Path=/; Domain=example.com; Max-Age=3600; HttpOnly; Secure; SameSite=Strict")
        
        c.String(200, "安全Cookie已设置")
    })
    
    r.Run()
}

二、Session管理 #

2.1 安装Session中间件 #

bash
go get github.com/gin-contrib/sessions
go get github.com/gin-contrib/sessions/cookie
go get github.com/gin-contrib/sessions/redis
go
package main

import (
    "github.com/gin-contrib/sessions"
    "github.com/gin-contrib/sessions/cookie"
    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()
    
    // 创建Cookie存储
    store := cookie.NewStore([]byte("secret"))
    
    // 配置Session
    store.Options(sessions.Options{
        MaxAge:   3600,  // 过期时间(秒)
        Path:     "/",
        Domain:   "localhost",
        Secure:   false,
        HttpOnly: true,
        SameSite: http.SameSiteLaxMode,
    })
    
    // 使用Session中间件
    r.Use(sessions.Sessions("mysession", store))
    
    r.GET("/set-session", func(c *gin.Context) {
        session := sessions.Default(c)
        session.Set("userId", "123")
        session.Set("role", "admin")
        session.Save()
        
        c.String(200, "Session已设置")
    })
    
    r.GET("/get-session", func(c *gin.Context) {
        session := sessions.Default(c)
        userId := session.Get("userId")
        role := session.Get("role")
        
        c.JSON(200, gin.H{
            "userId": userId,
            "role":   role,
        })
    })
    
    r.GET("/delete-session", func(c *gin.Context) {
        session := sessions.Default(c)
        session.Clear()
        session.Save()
        
        c.String(200, "Session已清除")
    })
    
    r.Run()
}

2.3 Redis Session #

go
package main

import (
    "github.com/gin-contrib/sessions"
    "github.com/gin-contrib/sessions/redis"
    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()
    
    // 创建Redis存储
    store, _ := redis.NewStore(10, "tcp", "localhost:6379", "", []byte("secret"))
    
    // 使用Session中间件
    r.Use(sessions.Sessions("mysession", store))
    
    r.GET("/session", func(c *gin.Context) {
        session := sessions.Default(c)
        session.Set("userId", "123")
        session.Save()
        
        c.String(200, "OK")
    })
    
    r.Run()
}

2.4 Session操作 #

go
func main() {
    r := gin.Default()
    
    store := cookie.NewStore([]byte("secret"))
    r.Use(sessions.Sessions("mysession", store))
    
    // 设置值
    r.GET("/set", func(c *gin.Context) {
        session := sessions.Default(c)
        session.Set("key", "value")
        session.Save()
        c.String(200, "OK")
    })
    
    // 获取值
    r.GET("/get", func(c *gin.Context) {
        session := sessions.Default(c)
        value := session.Get("key")
        c.String(200, "Value: %v", value)
    })
    
    // 删除单个键
    r.GET("/delete", func(c *gin.Context) {
        session := sessions.Default(c)
        session.Delete("key")
        session.Save()
        c.String(200, "Deleted")
    })
    
    // 清除所有
    r.GET("/clear", func(c *gin.Context) {
        session := sessions.Default(c)
        session.Clear()
        session.Save()
        c.String(200, "Cleared")
    })
    
    // 设置过期时间
    r.GET("/expire", func(c *gin.Context) {
        session := sessions.Default(c)
        session.Set("key", "value")
        session.Options(sessions.Options{
            MaxAge: 60, // 60秒后过期
        })
        session.Save()
        c.String(200, "OK")
    })
    
    r.Run()
}

三、JWT认证 #

3.1 JWT结构 #

text
JWT = Header.Payload.Signature

Header:  {"alg":"HS256","typ":"JWT"}
Payload: {"userId":"123","exp":1234567890}
Signature: HMACSHA256(base64(Header) + "." + base64(Payload), secret)

3.2 生成JWT #

go
package main

import (
    "time"
    
    "github.com/gin-gonic/gin"
    "github.com/golang-jwt/jwt/v5"
)

var jwtSecret = []byte("your-secret-key")

type Claims struct {
    UserID string `json:"userId"`
    Role   string `json:"role"`
    jwt.RegisteredClaims
}

func GenerateToken(userID, role string) (string, error) {
    claims := Claims{
        UserID: userID,
        Role:   role,
        RegisteredClaims: jwt.RegisteredClaims{
            ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
            IssuedAt:  jwt.NewNumericDate(time.Now()),
            Issuer:    "gin-demo",
        },
    }
    
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    return token.SignedString(jwtSecret)
}

func main() {
    r := gin.Default()
    
    r.POST("/login", func(c *gin.Context) {
        // 验证用户名密码
        username := c.PostForm("username")
        password := c.PostForm("password")
        
        if username == "admin" && password == "admin" {
            token, _ := GenerateToken("1", "admin")
            c.JSON(200, gin.H{
                "token": token,
            })
            return
        }
        
        c.JSON(401, gin.H{"error": "用户名或密码错误"})
    })
    
    r.Run()
}

3.3 验证JWT #

go
func ParseToken(tokenString string) (*Claims, error) {
    token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
        return jwtSecret, nil
    })
    
    if err != nil {
        return nil, err
    }
    
    if claims, ok := token.Claims.(*Claims); ok && token.Valid {
        return claims, nil
    }
    
    return nil, jwt.ErrSignatureInvalid
}

func JWTMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        tokenString := c.GetHeader("Authorization")
        
        if tokenString == "" {
            c.AbortWithStatusJSON(401, gin.H{
                "code":    401,
                "message": "未提供认证令牌",
            })
            return
        }
        
        // 移除Bearer前缀
        if strings.HasPrefix(tokenString, "Bearer ") {
            tokenString = strings.TrimPrefix(tokenString, "Bearer ")
        }
        
        claims, err := ParseToken(tokenString)
        if err != nil {
            c.AbortWithStatusJSON(401, gin.H{
                "code":    401,
                "message": "无效的认证令牌",
            })
            return
        }
        
        c.Set("userId", claims.UserID)
        c.Set("role", claims.Role)
        
        c.Next()
    }
}

func main() {
    r := gin.Default()
    
    // 公开路由
    r.POST("/login", loginHandler)
    
    // 需要认证的路由
    protected := r.Group("/api")
    protected.Use(JWTMiddleware())
    {
        protected.GET("/profile", func(c *gin.Context) {
            userId := c.GetString("userId")
            c.JSON(200, gin.H{"userId": userId})
        })
    }
    
    r.Run()
}

3.4 JWT刷新 #

go
func RefreshToken(tokenString string) (string, error) {
    claims, err := ParseToken(tokenString)
    if err != nil {
        return "", err
    }
    
    // 检查是否可以刷新
    if time.Until(claims.ExpiresAt.Time) > 30*time.Minute {
        return "", errors.New("token未过期,无需刷新")
    }
    
    // 生成新Token
    return GenerateToken(claims.UserID, claims.Role)
}

func main() {
    r := gin.Default()
    
    r.POST("/refresh", JWTMiddleware(), func(c *gin.Context) {
        oldToken := c.GetHeader("Authorization")
        oldToken = strings.TrimPrefix(oldToken, "Bearer ")
        
        newToken, err := RefreshToken(oldToken)
        if err != nil {
            c.JSON(400, gin.H{"error": err.Error()})
            return
        }
        
        c.JSON(200, gin.H{"token": newToken})
    })
    
    r.Run()
}

四、会话安全 #

4.1 安全配置 #

go
func main() {
    r := gin.Default()
    
    store := cookie.NewStore([]byte("your-secret-key-at-least-32-bytes"))
    
    // 安全配置
    store.Options(sessions.Options{
        MaxAge:   3600,
        Path:     "/",
        Secure:   true,              // 只在HTTPS传输
        HttpOnly: true,              // 禁止JS访问
        SameSite: http.SameSiteStrictMode, // 防止CSRF
    })
    
    r.Use(sessions.Sessions("sessionid", store))
    
    r.Run()
}

4.2 CSRF防护 #

bash
go get github.com/utrack/gin-csrf
go
package main

import (
    csrf "github.com/utrack/gin-csrf"
    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()
    
    // CSRF中间件
    r.Use(csrf.Middleware(csrf.Options{
        Secret: "csrf-secret",
        ErrorFunc: func(c *gin.Context) {
            c.String(400, "CSRF token mismatch")
            c.Abort()
        },
    }))
    
    // 获取CSRF Token
    r.GET("/csrf-token", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "csrfToken": csrf.GetToken(c),
        })
    })
    
    // 需要CSRF验证的路由
    r.POST("/protected", func(c *gin.Context) {
        c.String(200, "OK")
    })
    
    r.Run()
}

4.3 会话固定攻击防护 #

go
func main() {
    r := gin.Default()
    
    store := cookie.NewStore([]byte("secret"))
    r.Use(sessions.Sessions("mysession", store))
    
    r.POST("/login", func(c *gin.Context) {
        session := sessions.Default(c)
        
        // 登录成功后重新生成Session ID
        session.Clear()
        session.Set("userId", "123")
        session.Save()
        
        c.String(200, "OK")
    })
    
    r.Run()
}

五、完整认证示例 #

5.1 用户登录 #

go
package main

import (
    "time"
    
    "github.com/gin-contrib/sessions"
    "github.com/gin-contrib/sessions/cookie"
    "github.com/gin-gonic/gin"
)

type User struct {
    ID       string
    Username string
    Password string
}

var users = map[string]User{
    "admin": {ID: "1", Username: "admin", Password: "admin123"},
}

func main() {
    r := gin.Default()
    
    store := cookie.NewStore([]byte("secret"))
    r.Use(sessions.Sessions("sessionid", store))
    
    r.POST("/login", func(c *gin.Context) {
        username := c.PostForm("username")
        password := c.PostForm("password")
        
        user, exists := users[username]
        if !exists || user.Password != password {
            c.JSON(401, gin.H{"error": "用户名或密码错误"})
            return
        }
        
        session := sessions.Default(c)
        session.Set("userId", user.ID)
        session.Set("username", user.Username)
        session.Save()
        
        c.JSON(200, gin.H{
            "message": "登录成功",
            "user": gin.H{
                "id":       user.ID,
                "username": user.Username,
            },
        })
    })
    
    r.GET("/logout", func(c *gin.Context) {
        session := sessions.Default(c)
        session.Clear()
        session.Save()
        
        c.JSON(200, gin.H{"message": "已退出登录"})
    })
    
    r.GET("/profile", AuthRequired(), func(c *gin.Context) {
        session := sessions.Default(c)
        userId := session.Get("userId")
        username := session.Get("username")
        
        c.JSON(200, gin.H{
            "userId":   userId,
            "username": username,
        })
    })
    
    r.Run()
}

func AuthRequired() gin.HandlerFunc {
    return func(c *gin.Context) {
        session := sessions.Default(c)
        userId := session.Get("userId")
        
        if userId == nil {
            c.AbortWithStatusJSON(401, gin.H{"error": "请先登录"})
            return
        }
        
        c.Next()
    }
}

六、总结 #

6.1 核心要点 #

要点 说明
Cookie 客户端存储
Session 服务端存储
JWT 无状态认证
安全 HttpOnly、Secure、SameSite

6.2 最佳实践 #

实践 说明
使用HTTPS 保护传输安全
HttpOnly 防止XSS攻击
SameSite 防止CSRF攻击
短过期时间 减少被盗风险

6.3 下一步 #

现在你已经掌握了Cookie与Session,接下来让我们学习 文件上传下载,了解文件处理!

最后更新:2026-03-28