Files
labmem d375463de5 refactor(go)!: 分离练习区与独立项目区
- 将现有练习及 module 文件迁移至 go/practice,保持代码和依赖不变
- 新增 go/projects 项目区说明,更新目录约定与开发命令

BREAKING CHANGE: Go 练习命令需改在 go/practice 目录下执行
2026-10-15 22:26:57 +08:00

117 lines
3.6 KiB
Go

package main
import (
"net/http"
"time"
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin"
)
var (
mockUsers = map[string]string{
"zhangsan": "123456",
"lisi": "654321",
}
// secretKey = "your-secret-key"
hmacSecretKey = "5KiinhGcvoXFmgOttvgMWd9ugadE0KVP"
blockSecretKey = "OLZmfTWHGyjCfJoH1Y8SjbSY"
)
const sessionTTL = 60 * 5
func sessionOptions(maxAge int) sessions.Options {
return sessions.Options{
Path: "/",
Domain: "", // 设置为 "" 表示当前域名
MaxAge: maxAge, // 单位为秒。0 表示不写Max-Age(浏览器会话cookie); -1 表示删除 cookie
Secure: false, // 生产走 HTTPS 时改 true
HttpOnly: true, // true 表示禁止 JS 读取 cookie
SameSite: http.SameSiteStrictMode, // 严格模式,仅在同源请求中发送 cookie
}
}
// 代码来自文章 https://www.cnblogs.com/XY-Heruo/p/22864951
func main() {
r := gin.Default()
// Create cookie-based session store with a secret key
// 传单个密钥时, 只做签名认证,不加密cookie内容. 认证密钥建议 32 或 64 字节
// 传一对密钥时,第一个用于认证(HMAC),第二个用于加密(AES-128, AES-192, AES-256)。加密密钥必须是16、24或32字节长度,对应 AES-128/192/256。
// 多对密钥用于密钥轮换,第一对用于当前加密和认证,后续的用于解密和认证旧的cookie。Example: cookie.NewStore([]byte("newAuth"), []byte("newEnc"), []byte("oldAuth"), []byte("oldEnc"))
store := cookie.NewStore([]byte(hmacSecretKey), []byte(blockSecretKey))
store.Options(sessionOptions(sessionTTL)) // 全局设置 cookie 过期时间为 5 分钟
r.Use(sessions.Sessions("mysession", store))
r.POST("/login", login)
auth := r.Group("/", authRequired())
auth.GET("/profile", profile)
auth.POST("/logout", logout)
r.Run(":8080")
}
func login(c *gin.Context) {
session := sessions.Default(c)
username := c.PostForm("username")
password := c.PostForm("password")
if username == "" || password == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "username or password is empty"})
return
}
if mockUsers[username] != password {
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid username or password"})
return
}
session.Set("user", username)
session.Set("expire_at", time.Now().Add(time.Second*sessionTTL).Unix()) // 设置过期时间
if err := session.Save(); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "save session failed"})
return
}
c.JSON(http.StatusOK, gin.H{"message": "logged in"})
}
func logout(c *gin.Context) {
session := sessions.Default(c)
session.Clear()
session.Options(sessionOptions(-1)) // 让浏览器删除 cookie
if err := session.Save(); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "save session failed"})
return
}
c.JSON(http.StatusOK, gin.H{"message": "logged out"})
}
func profile(c *gin.Context) {
session := sessions.Default(c)
user := session.Get("user").(string)
c.JSON(http.StatusOK, gin.H{"user": user})
}
func authRequired() gin.HandlerFunc {
return func(c *gin.Context) {
session := sessions.Default(c)
if session.Get("user") == nil {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
return
}
if expireAt, ok := session.Get("expire_at").(int64); !ok || time.Now().Unix() > expireAt {
session.Clear()
session.Options(sessionOptions(-1))
session.Save()
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "expired session"})
return
}
c.Next()
}
}