topfans/backend/gateway/middleware/auth_middleware.go
2026-06-03 22:19:22 +08:00

123 lines
3.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package middleware
import (
"net/http"
"strings"
"github.com/gin-gonic/gin"
"github.com/topfans/backend/gateway/pkg/response"
"github.com/topfans/backend/pkg/database"
"github.com/topfans/backend/pkg/jwt"
"github.com/topfans/backend/pkg/logger"
"go.uber.org/zap"
)
// AuthMiddleware JWT 认证中间件
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 1. 从 HTTP Header 提取 Token
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
logger.Logger.Warn("Missing authorization token",
zap.String("path", c.Request.URL.Path),
zap.String("method", c.Request.Method),
)
response.Unauthorized(c, "未携带访问令牌")
c.Abort()
return
}
// 2. 去除 "Bearer " 前缀
token := strings.TrimPrefix(authHeader, "Bearer ")
if token == authHeader {
// 没有 Bearer 前缀,也尝试解析(兼容性)
token = authHeader
}
// 3. 验证 Token
claims, err := jwt.ParseToken(token)
if err != nil {
logger.Logger.Warn("Invalid token",
zap.String("path", c.Request.URL.Path),
zap.String("method", c.Request.Method),
zap.Error(err),
)
response.Unauthorized(c, "访问令牌无效或已过期")
c.Abort()
return
}
// 4. 检查 Token 是否在黑名单Redis 不可用时降级放行,安全由生产环境 Redis 保证)
isBlacklisted, bannedUserID, banReason, err := database.IsBlacklisted(c.Request.Context(), token)
if err != nil {
logger.Logger.Warn("Blacklist check failed, allowing request (Redis unavailable)",
zap.String("path", c.Request.URL.Path),
zap.Error(err),
)
} else if isBlacklisted {
logger.Logger.Warn("Token is blacklisted",
zap.Int64("banned_user_id", bannedUserID),
zap.String("ban_reason", banReason),
zap.String("path", c.Request.URL.Path),
)
response.Unauthorized(c, "账号已被封禁")
c.Abort()
return
}
// 5. 将用户信息存入 gin.Context
c.Set("user_id", claims.UserID)
c.Set("star_id", claims.StarID)
c.Set("token_updated_at", claims.UpdatedAt)
logger.Logger.Debug("User authenticated",
zap.Int64("user_id", claims.UserID),
zap.Int64("star_id", claims.StarID),
zap.String("path", c.Request.URL.Path),
)
c.Next()
}
}
// CORSMiddleware CORS 中间件
func CORSMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
origin := c.Request.Header.Get("Origin")
if origin == "" {
origin = "*"
}
c.Writer.Header().Set("Access-Control-Allow-Origin", origin)
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(http.StatusNoContent)
return
}
c.Next()
}
}
// LoggerMiddleware 日志中间件
func LoggerMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 记录请求开始
logger.Logger.Info("Request received",
zap.String("method", c.Request.Method),
zap.String("path", c.Request.URL.Path),
zap.String("ip", c.ClientIP()),
)
c.Next()
// 记录请求结束
logger.Logger.Info("Request completed",
zap.String("method", c.Request.Method),
zap.String("path", c.Request.URL.Path),
zap.Int("status", c.Writer.Status()),
)
}
}