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
2.2 Cookie Session #
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