- 将现有练习及 module 文件迁移至 go/practice,保持代码和依赖不变 - 新增 go/projects 项目区说明,更新目录约定与开发命令 BREAKING CHANGE: Go 练习命令需改在 go/practice 目录下执行
117 lines
3.6 KiB
Go
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()
|
|
}
|
|
}
|