Files
NetworkAuth/utils/csrf.go

206 lines
4.8 KiB
Go
Raw Normal View History

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-27 23:12:15 +08:00
// ============================================================================
// 常量定义
// ============================================================================
const (
CSRFTokenLength = 32
CSRFCookieName = "csrf_token"
CSRFHeaderName = "X-CSRF-Token"
CSRFFormField = "csrf_token"
)
2025-10-27 23:12:15 +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
// ============================================================================
// 公共函数
// ============================================================================
// 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 14:48:02 +08:00
// GetCSRFTokenFromRequest 从Gin请求中获取CSRF令牌
// 优先级Header > Form > Cookie
2025-10-26 14:48:02 +08:00
func GetCSRFTokenFromRequest(c *gin.Context) string {
// 1. 从Header获取
2025-10-26 14:48:02 +08:00
if token := c.GetHeader(CSRFHeaderName); token != "" {
return token
}
// 2. 从Form获取
2025-10-26 14:48:02 +08:00
if token := c.PostForm(CSRFFormField); token != "" {
return token
}
// 3. 从Cookie获取作为备选
2025-10-26 14:48:02 +08:00
if cookie, err := c.Cookie(CSRFCookieName); err == nil {
return cookie
}
return ""
}
// GetCSRFTokenFromCookie 从Cookie中获取CSRF令牌
2025-10-26 14:48:02 +08:00
func GetCSRFTokenFromCookie(c *gin.Context) string {
cookie, err := c.Cookie(CSRFCookieName)
if err != nil {
return ""
}
2025-10-26 14:48:02 +08:00
return cookie
}
// ValidateCSRFToken 验证CSRF令牌
2025-10-26 14:48:02 +08:00
func ValidateCSRFToken(c *gin.Context) bool {
// 获取Cookie中的令牌服务器端存储的
2025-10-26 14:48:02 +08:00
cookieToken := GetCSRFTokenFromCookie(c)
if cookieToken == "" {
return false
}
// 获取请求中的令牌(客户端提交的)
2025-10-26 14:48:02 +08:00
requestToken := GetCSRFTokenFromRequest(c)
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) {
// 对于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 {
// 生成新的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()
return
}
2025-10-26 14:48:02 +08:00
SetCSRFToken(c, token)
c.Next()
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()
return
}
// 验证通过,继续处理请求
2025-10-26 14:48:02 +08:00
c.Next()
}
}
// 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()
return
}
2025-10-26 14:48:02 +08:00
c.Next()
}
}
// GetCSRFTokenForTemplate 获取用于模板的CSRF令牌
2025-10-26 14:48:02 +08:00
func GetCSRFTokenForTemplate(c *gin.Context) string {
// 尝试从Cookie获取现有令牌
2025-10-26 14:48:02 +08:00
if token := GetCSRFTokenFromCookie(c); token != "" {
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,
})
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,
})
return
}
// 设置令牌到Cookie和响应头
2025-10-26 14:48:02 +08:00
SetCSRFToken(c, token)
// 返回令牌给前端
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-27 23:12:15 +08:00
}