2025-10-26 03:05:27 +08:00
|
|
|
|
package utils
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"crypto/rand"
|
|
|
|
|
|
"crypto/subtle"
|
|
|
|
|
|
"encoding/base64"
|
|
|
|
|
|
"net/http"
|
2025-10-26 14:48:02 +08:00
|
|
|
|
|
|
|
|
|
|
"github.com/gin-gonic/gin"
|
2025-10-26 03:05:27 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
2025-10-27 23:12:15 +08:00
|
|
|
|
// ============================================================================
|
|
|
|
|
|
// 常量定义
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
2025-10-26 03:05:27 +08:00
|
|
|
|
const (
|
|
|
|
|
|
CSRFTokenLength = 32
|
|
|
|
|
|
CSRFCookieName = "csrf_token"
|
|
|
|
|
|
CSRFHeaderName = "X-CSRF-Token"
|
|
|
|
|
|
CSRFFormField = "csrf_token"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-10-27 23:12:15 +08:00
|
|
|
|
// ============================================================================
|
|
|
|
|
|
// 私有函数
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
2025-10-26 03:05:27 +08:00
|
|
|
|
// generateRandomBytes 生成指定长度的随机字节
|
|
|
|
|
|
func generateRandomBytes(length int) ([]byte, error) {
|
|
|
|
|
|
bytes := make([]byte, length)
|
|
|
|
|
|
_, err := rand.Read(bytes)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
return bytes, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-27 23:12:15 +08:00
|
|
|
|
// ============================================================================
|
|
|
|
|
|
// 公共函数
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
2025-10-26 03:05:27 +08:00
|
|
|
|
// GenerateCSRFToken 生成CSRF令牌
|
|
|
|
|
|
func GenerateCSRFToken() (string, error) {
|
|
|
|
|
|
bytes, err := generateRandomBytes(CSRFTokenLength)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return "", err
|
|
|
|
|
|
}
|
|
|
|
|
|
return base64.URLEncoding.EncodeToString(bytes), nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// SetCSRFToken 设置CSRF令牌到Cookie和响应头
|
2025-10-26 14:48:02 +08:00
|
|
|
|
func SetCSRFToken(c *gin.Context, token string) {
|
|
|
|
|
|
c.SetCookie(CSRFCookieName, token, 3600*24, "/", "", false, true)
|
|
|
|
|
|
c.Header(CSRFHeaderName, token)
|
2025-10-26 03:05:27 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-26 14:48:02 +08:00
|
|
|
|
// GetCSRFTokenFromRequest 从Gin请求中获取CSRF令牌
|
2025-10-26 03:05:27 +08:00
|
|
|
|
// 优先级:Header > Form > Cookie
|
2025-10-26 14:48:02 +08:00
|
|
|
|
func GetCSRFTokenFromRequest(c *gin.Context) string {
|
2025-10-26 03:05:27 +08:00
|
|
|
|
// 1. 从Header获取
|
2025-10-26 14:48:02 +08:00
|
|
|
|
if token := c.GetHeader(CSRFHeaderName); token != "" {
|
2025-10-26 03:05:27 +08:00
|
|
|
|
return token
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 从Form获取
|
2025-10-26 14:48:02 +08:00
|
|
|
|
if token := c.PostForm(CSRFFormField); token != "" {
|
2025-10-26 03:05:27 +08:00
|
|
|
|
return token
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 从Cookie获取(作为备选)
|
2025-10-26 14:48:02 +08:00
|
|
|
|
if cookie, err := c.Cookie(CSRFCookieName); err == nil {
|
|
|
|
|
|
return cookie
|
2025-10-26 03:05:27 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return ""
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetCSRFTokenFromCookie 从Cookie中获取CSRF令牌
|
2025-10-26 14:48:02 +08:00
|
|
|
|
func GetCSRFTokenFromCookie(c *gin.Context) string {
|
|
|
|
|
|
cookie, err := c.Cookie(CSRFCookieName)
|
2025-10-26 03:05:27 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return ""
|
|
|
|
|
|
}
|
2025-10-26 14:48:02 +08:00
|
|
|
|
return cookie
|
2025-10-26 03:05:27 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ValidateCSRFToken 验证CSRF令牌
|
2025-10-26 14:48:02 +08:00
|
|
|
|
func ValidateCSRFToken(c *gin.Context) bool {
|
2025-10-26 03:05:27 +08:00
|
|
|
|
// 获取Cookie中的令牌(服务器端存储的)
|
2025-10-26 14:48:02 +08:00
|
|
|
|
cookieToken := GetCSRFTokenFromCookie(c)
|
2025-10-26 03:05:27 +08:00
|
|
|
|
if cookieToken == "" {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取请求中的令牌(客户端提交的)
|
2025-10-26 14:48:02 +08:00
|
|
|
|
requestToken := GetCSRFTokenFromRequest(c)
|
2025-10-26 03:05:27 +08:00
|
|
|
|
if requestToken == "" {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 使用常量时间比较防止时序攻击
|
|
|
|
|
|
return subtle.ConstantTimeCompare([]byte(cookieToken), []byte(requestToken)) == 1
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// CSRFProtection CSRF保护中间件
|
2025-10-26 14:48:02 +08:00
|
|
|
|
func CSRFProtection() gin.HandlerFunc {
|
|
|
|
|
|
return func(c *gin.Context) {
|
2025-10-26 03:05:27 +08:00
|
|
|
|
// 对于GET、HEAD、OPTIONS请求,只生成令牌,不验证
|
2025-10-26 14:48:02 +08:00
|
|
|
|
if c.Request.Method == http.MethodGet || c.Request.Method == http.MethodHead || c.Request.Method == http.MethodOptions {
|
2025-10-26 03:05:27 +08:00
|
|
|
|
// 生成新的CSRF令牌
|
|
|
|
|
|
token, err := GenerateCSRFToken()
|
|
|
|
|
|
if err != nil {
|
2025-10-26 14:48:02 +08:00
|
|
|
|
c.JSON(http.StatusInternalServerError, gin.H{
|
|
|
|
|
|
"code": 1,
|
|
|
|
|
|
"msg": "Internal Server Error",
|
|
|
|
|
|
"data": nil,
|
|
|
|
|
|
})
|
|
|
|
|
|
c.Abort()
|
2025-10-26 03:05:27 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
2025-10-26 14:48:02 +08:00
|
|
|
|
SetCSRFToken(c, token)
|
|
|
|
|
|
c.Next()
|
2025-10-26 03:05:27 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 对于POST、PUT、DELETE等修改性请求,验证CSRF令牌
|
2025-10-26 14:48:02 +08:00
|
|
|
|
if !ValidateCSRFToken(c) {
|
|
|
|
|
|
c.JSON(http.StatusForbidden, gin.H{
|
|
|
|
|
|
"code": 1,
|
|
|
|
|
|
"msg": "CSRF令牌验证失败",
|
|
|
|
|
|
"data": nil,
|
|
|
|
|
|
})
|
|
|
|
|
|
c.Abort()
|
2025-10-26 03:05:27 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证通过,继续处理请求
|
2025-10-26 14:48:02 +08:00
|
|
|
|
c.Next()
|
2025-10-26 03:05:27 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// RequireCSRFToken 要求CSRF令牌的中间件(用于特定路由)
|
2025-10-26 14:48:02 +08:00
|
|
|
|
func RequireCSRFToken() gin.HandlerFunc {
|
|
|
|
|
|
return func(c *gin.Context) {
|
|
|
|
|
|
if !ValidateCSRFToken(c) {
|
|
|
|
|
|
c.JSON(http.StatusForbidden, gin.H{
|
|
|
|
|
|
"code": 1,
|
|
|
|
|
|
"msg": "CSRF令牌验证失败",
|
|
|
|
|
|
"data": nil,
|
|
|
|
|
|
})
|
|
|
|
|
|
c.Abort()
|
2025-10-26 03:05:27 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
2025-10-26 14:48:02 +08:00
|
|
|
|
c.Next()
|
2025-10-26 03:05:27 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetCSRFTokenForTemplate 获取用于模板的CSRF令牌
|
2025-10-26 14:48:02 +08:00
|
|
|
|
func GetCSRFTokenForTemplate(c *gin.Context) string {
|
2025-10-26 03:05:27 +08:00
|
|
|
|
// 尝试从Cookie获取现有令牌
|
2025-10-26 14:48:02 +08:00
|
|
|
|
if token := GetCSRFTokenFromCookie(c); token != "" {
|
2025-10-26 03:05:27 +08:00
|
|
|
|
return token
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果没有现有令牌,生成新的(但不设置到响应中)
|
|
|
|
|
|
token, err := GenerateCSRFToken()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return ""
|
|
|
|
|
|
}
|
|
|
|
|
|
return token
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// CSRFTokenHandler 专门用于获取CSRF令牌的API端点
|
2025-10-26 14:48:02 +08:00
|
|
|
|
func CSRFTokenHandler(c *gin.Context) {
|
|
|
|
|
|
if c.Request.Method != http.MethodGet {
|
|
|
|
|
|
c.JSON(http.StatusMethodNotAllowed, gin.H{
|
|
|
|
|
|
"code": 1,
|
|
|
|
|
|
"msg": "只支持GET请求",
|
|
|
|
|
|
"data": nil,
|
|
|
|
|
|
})
|
2025-10-26 03:05:27 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 生成新的CSRF令牌
|
|
|
|
|
|
token, err := GenerateCSRFToken()
|
|
|
|
|
|
if err != nil {
|
2025-10-26 14:48:02 +08:00
|
|
|
|
c.JSON(http.StatusInternalServerError, gin.H{
|
|
|
|
|
|
"code": 1,
|
|
|
|
|
|
"msg": "生成CSRF令牌失败",
|
|
|
|
|
|
"data": nil,
|
|
|
|
|
|
})
|
2025-10-26 03:05:27 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 设置令牌到Cookie和响应头
|
2025-10-26 14:48:02 +08:00
|
|
|
|
SetCSRFToken(c, token)
|
2025-10-26 03:05:27 +08:00
|
|
|
|
|
|
|
|
|
|
// 返回令牌给前端
|
2025-10-26 14:48:02 +08:00
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
|
|
|
|
"code": 0,
|
|
|
|
|
|
"msg": "CSRF令牌生成成功",
|
|
|
|
|
|
"data": gin.H{
|
|
|
|
|
|
"csrf_token": token,
|
|
|
|
|
|
},
|
2025-10-26 03:05:27 +08:00
|
|
|
|
})
|
2025-10-27 23:12:15 +08:00
|
|
|
|
}
|