2025-06-30 19:21:56 +08:00
|
|
|
|
package middleware
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"net/http"
|
|
|
|
|
|
"strings"
|
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
|
|
"tyapi-server/internal/config"
|
|
|
|
|
|
|
|
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
|
|
"github.com/golang-jwt/jwt/v5"
|
|
|
|
|
|
"go.uber.org/zap"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// JWTAuthMiddleware JWT认证中间件
|
|
|
|
|
|
type JWTAuthMiddleware struct {
|
|
|
|
|
|
config *config.Config
|
|
|
|
|
|
logger *zap.Logger
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// NewJWTAuthMiddleware 创建JWT认证中间件
|
|
|
|
|
|
func NewJWTAuthMiddleware(cfg *config.Config, logger *zap.Logger) *JWTAuthMiddleware {
|
|
|
|
|
|
return &JWTAuthMiddleware{
|
|
|
|
|
|
config: cfg,
|
|
|
|
|
|
logger: logger,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetName 返回中间件名称
|
|
|
|
|
|
func (m *JWTAuthMiddleware) GetName() string {
|
|
|
|
|
|
return "jwt_auth"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-20 20:53:26 +08:00
|
|
|
|
// GetExpiresIn 返回JWT过期时间
|
|
|
|
|
|
func (m *JWTAuthMiddleware) GetExpiresIn() time.Duration {
|
|
|
|
|
|
return m.config.JWT.ExpiresIn
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-30 19:21:56 +08:00
|
|
|
|
// GetPriority 返回中间件优先级
|
|
|
|
|
|
func (m *JWTAuthMiddleware) GetPriority() int {
|
|
|
|
|
|
return 60 // 中等优先级,在日志之后,业务处理之前
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Handle 返回中间件处理函数
|
|
|
|
|
|
func (m *JWTAuthMiddleware) Handle() gin.HandlerFunc {
|
|
|
|
|
|
return func(c *gin.Context) {
|
|
|
|
|
|
// 获取Authorization头部
|
|
|
|
|
|
authHeader := c.GetHeader("Authorization")
|
|
|
|
|
|
if authHeader == "" {
|
2025-07-02 16:17:59 +08:00
|
|
|
|
m.respondUnauthorized(c, "缺少认证头部")
|
2025-06-30 19:21:56 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查Bearer前缀
|
|
|
|
|
|
const bearerPrefix = "Bearer "
|
|
|
|
|
|
if !strings.HasPrefix(authHeader, bearerPrefix) {
|
2025-07-02 16:17:59 +08:00
|
|
|
|
m.respondUnauthorized(c, "认证头部格式无效")
|
2025-06-30 19:21:56 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 提取token
|
|
|
|
|
|
tokenString := authHeader[len(bearerPrefix):]
|
|
|
|
|
|
if tokenString == "" {
|
2025-07-02 16:17:59 +08:00
|
|
|
|
m.respondUnauthorized(c, "缺少认证令牌")
|
2025-06-30 19:21:56 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证token
|
|
|
|
|
|
claims, err := m.validateToken(tokenString)
|
|
|
|
|
|
if err != nil {
|
2025-07-02 16:17:59 +08:00
|
|
|
|
m.logger.Warn("无效的认证令牌",
|
2025-06-30 19:21:56 +08:00
|
|
|
|
zap.Error(err),
|
|
|
|
|
|
zap.String("request_id", c.GetString("request_id")))
|
2025-07-02 16:17:59 +08:00
|
|
|
|
m.respondUnauthorized(c, "认证令牌无效")
|
2025-06-30 19:21:56 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 将用户信息添加到上下文
|
|
|
|
|
|
c.Set("user_id", claims.UserID)
|
|
|
|
|
|
c.Set("username", claims.Username)
|
|
|
|
|
|
c.Set("email", claims.Email)
|
2025-07-20 20:53:26 +08:00
|
|
|
|
c.Set("phone", claims.Phone)
|
|
|
|
|
|
c.Set("user_type", claims.UserType)
|
2025-06-30 19:21:56 +08:00
|
|
|
|
c.Set("token_claims", claims)
|
|
|
|
|
|
|
|
|
|
|
|
c.Next()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// IsGlobal 是否为全局中间件
|
|
|
|
|
|
func (m *JWTAuthMiddleware) IsGlobal() bool {
|
|
|
|
|
|
return false // 不是全局中间件,需要手动应用到需要认证的路由
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// JWTClaims JWT声明结构
|
|
|
|
|
|
type JWTClaims struct {
|
|
|
|
|
|
UserID string `json:"user_id"`
|
|
|
|
|
|
Username string `json:"username"`
|
|
|
|
|
|
Email string `json:"email"`
|
2025-07-20 20:53:26 +08:00
|
|
|
|
Phone string `json:"phone"`
|
|
|
|
|
|
UserType string `json:"user_type"` // 新增:用户类型
|
2025-06-30 19:21:56 +08:00
|
|
|
|
jwt.RegisteredClaims
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// validateToken 验证JWT token
|
|
|
|
|
|
func (m *JWTAuthMiddleware) validateToken(tokenString string) (*JWTClaims, error) {
|
|
|
|
|
|
token, err := jwt.ParseWithClaims(tokenString, &JWTClaims{}, func(token *jwt.Token) (interface{}, error) {
|
|
|
|
|
|
// 验证签名方法
|
|
|
|
|
|
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
|
|
|
|
|
return nil, jwt.ErrSignatureInvalid
|
|
|
|
|
|
}
|
|
|
|
|
|
return []byte(m.config.JWT.Secret), nil
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
claims, ok := token.Claims.(*JWTClaims)
|
|
|
|
|
|
if !ok || !token.Valid {
|
|
|
|
|
|
return nil, jwt.ErrSignatureInvalid
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return claims, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// respondUnauthorized 返回未授权响应
|
|
|
|
|
|
func (m *JWTAuthMiddleware) respondUnauthorized(c *gin.Context, message string) {
|
|
|
|
|
|
c.JSON(http.StatusUnauthorized, gin.H{
|
|
|
|
|
|
"success": false,
|
2025-07-02 16:17:59 +08:00
|
|
|
|
"message": "认证失败",
|
2025-06-30 19:21:56 +08:00
|
|
|
|
"error": message,
|
|
|
|
|
|
"request_id": c.GetString("request_id"),
|
|
|
|
|
|
"timestamp": time.Now().Unix(),
|
|
|
|
|
|
})
|
|
|
|
|
|
c.Abort()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GenerateToken 生成JWT token
|
2025-07-20 20:53:26 +08:00
|
|
|
|
func (m *JWTAuthMiddleware) GenerateToken(userID, phone, email, userType string) (string, error) {
|
2025-06-30 19:21:56 +08:00
|
|
|
|
now := time.Now()
|
|
|
|
|
|
|
|
|
|
|
|
claims := &JWTClaims{
|
|
|
|
|
|
UserID: userID,
|
2025-07-20 20:53:26 +08:00
|
|
|
|
Username: phone, // 普通用户用手机号,管理员用用户名
|
2025-06-30 19:21:56 +08:00
|
|
|
|
Email: email,
|
2025-07-20 20:53:26 +08:00
|
|
|
|
Phone: phone,
|
|
|
|
|
|
UserType: userType, // 新增:用户类型
|
2025-06-30 19:21:56 +08:00
|
|
|
|
RegisteredClaims: jwt.RegisteredClaims{
|
|
|
|
|
|
Issuer: "tyapi-server",
|
|
|
|
|
|
Subject: userID,
|
|
|
|
|
|
Audience: []string{"tyapi-client"},
|
|
|
|
|
|
ExpiresAt: jwt.NewNumericDate(now.Add(m.config.JWT.ExpiresIn)),
|
|
|
|
|
|
NotBefore: jwt.NewNumericDate(now),
|
|
|
|
|
|
IssuedAt: jwt.NewNumericDate(now),
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
|
|
|
|
|
return token.SignedString([]byte(m.config.JWT.Secret))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GenerateRefreshToken 生成刷新token
|
|
|
|
|
|
func (m *JWTAuthMiddleware) GenerateRefreshToken(userID string) (string, error) {
|
|
|
|
|
|
now := time.Now()
|
|
|
|
|
|
|
|
|
|
|
|
claims := &jwt.RegisteredClaims{
|
|
|
|
|
|
Issuer: "tyapi-server",
|
|
|
|
|
|
Subject: userID,
|
|
|
|
|
|
Audience: []string{"tyapi-refresh"},
|
|
|
|
|
|
ExpiresAt: jwt.NewNumericDate(now.Add(m.config.JWT.RefreshExpiresIn)),
|
|
|
|
|
|
NotBefore: jwt.NewNumericDate(now),
|
|
|
|
|
|
IssuedAt: jwt.NewNumericDate(now),
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
|
|
|
|
|
return token.SignedString([]byte(m.config.JWT.Secret))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ValidateRefreshToken 验证刷新token
|
|
|
|
|
|
func (m *JWTAuthMiddleware) ValidateRefreshToken(tokenString string) (string, error) {
|
|
|
|
|
|
token, err := jwt.ParseWithClaims(tokenString, &jwt.RegisteredClaims{}, func(token *jwt.Token) (interface{}, error) {
|
|
|
|
|
|
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
|
|
|
|
|
return nil, jwt.ErrSignatureInvalid
|
|
|
|
|
|
}
|
|
|
|
|
|
return []byte(m.config.JWT.Secret), nil
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return "", err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
claims, ok := token.Claims.(*jwt.RegisteredClaims)
|
|
|
|
|
|
if !ok || !token.Valid {
|
|
|
|
|
|
return "", jwt.ErrSignatureInvalid
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否为刷新token
|
|
|
|
|
|
if len(claims.Audience) == 0 || claims.Audience[0] != "tyapi-refresh" {
|
|
|
|
|
|
return "", jwt.ErrSignatureInvalid
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return claims.Subject, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// OptionalAuthMiddleware 可选认证中间件(用户可能登录也可能未登录)
|
|
|
|
|
|
type OptionalAuthMiddleware struct {
|
|
|
|
|
|
jwtAuth *JWTAuthMiddleware
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// NewOptionalAuthMiddleware 创建可选认证中间件
|
|
|
|
|
|
func NewOptionalAuthMiddleware(jwtAuth *JWTAuthMiddleware) *OptionalAuthMiddleware {
|
|
|
|
|
|
return &OptionalAuthMiddleware{
|
|
|
|
|
|
jwtAuth: jwtAuth,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetName 返回中间件名称
|
|
|
|
|
|
func (m *OptionalAuthMiddleware) GetName() string {
|
|
|
|
|
|
return "optional_auth"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetPriority 返回中间件优先级
|
|
|
|
|
|
func (m *OptionalAuthMiddleware) GetPriority() int {
|
|
|
|
|
|
return 60 // 与JWT认证中间件相同
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Handle 返回中间件处理函数
|
|
|
|
|
|
func (m *OptionalAuthMiddleware) Handle() gin.HandlerFunc {
|
|
|
|
|
|
return func(c *gin.Context) {
|
|
|
|
|
|
// 获取Authorization头部
|
|
|
|
|
|
authHeader := c.GetHeader("Authorization")
|
|
|
|
|
|
if authHeader == "" {
|
|
|
|
|
|
// 没有认证头部,设置匿名用户标识
|
|
|
|
|
|
c.Set("is_authenticated", false)
|
|
|
|
|
|
c.Next()
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查Bearer前缀
|
|
|
|
|
|
const bearerPrefix = "Bearer "
|
|
|
|
|
|
if !strings.HasPrefix(authHeader, bearerPrefix) {
|
|
|
|
|
|
c.Set("is_authenticated", false)
|
|
|
|
|
|
c.Next()
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 提取并验证token
|
|
|
|
|
|
tokenString := authHeader[len(bearerPrefix):]
|
|
|
|
|
|
claims, err := m.jwtAuth.validateToken(tokenString)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
// token无效,但不返回错误,设置为未认证
|
|
|
|
|
|
c.Set("is_authenticated", false)
|
|
|
|
|
|
c.Next()
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// token有效,设置用户信息
|
|
|
|
|
|
c.Set("is_authenticated", true)
|
|
|
|
|
|
c.Set("user_id", claims.UserID)
|
|
|
|
|
|
c.Set("username", claims.Username)
|
|
|
|
|
|
c.Set("email", claims.Email)
|
2025-07-20 20:53:26 +08:00
|
|
|
|
c.Set("phone", claims.Phone)
|
|
|
|
|
|
c.Set("user_type", claims.UserType)
|
2025-06-30 19:21:56 +08:00
|
|
|
|
c.Set("token_claims", claims)
|
|
|
|
|
|
|
|
|
|
|
|
c.Next()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// IsGlobal 是否为全局中间件
|
|
|
|
|
|
func (m *OptionalAuthMiddleware) IsGlobal() bool {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
2025-07-20 20:53:26 +08:00
|
|
|
|
|
|
|
|
|
|
// AdminAuthMiddleware 管理员认证中间件
|
|
|
|
|
|
type AdminAuthMiddleware struct {
|
|
|
|
|
|
jwtAuth *JWTAuthMiddleware
|
|
|
|
|
|
logger *zap.Logger
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// NewAdminAuthMiddleware 创建管理员认证中间件
|
|
|
|
|
|
func NewAdminAuthMiddleware(jwtAuth *JWTAuthMiddleware, logger *zap.Logger) *AdminAuthMiddleware {
|
|
|
|
|
|
return &AdminAuthMiddleware{
|
|
|
|
|
|
jwtAuth: jwtAuth,
|
|
|
|
|
|
logger: logger,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetName 返回中间件名称
|
|
|
|
|
|
func (m *AdminAuthMiddleware) GetName() string {
|
|
|
|
|
|
return "admin_auth"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetPriority 返回中间件优先级
|
|
|
|
|
|
func (m *AdminAuthMiddleware) GetPriority() int {
|
|
|
|
|
|
return 60 // 与JWT认证中间件相同
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Handle 管理员认证处理
|
|
|
|
|
|
func (m *AdminAuthMiddleware) Handle() gin.HandlerFunc {
|
|
|
|
|
|
return func(c *gin.Context) {
|
|
|
|
|
|
// 首先进行JWT认证
|
|
|
|
|
|
authHeader := c.GetHeader("Authorization")
|
|
|
|
|
|
if authHeader == "" {
|
|
|
|
|
|
m.respondUnauthorized(c, "缺少认证头部")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查Bearer前缀
|
|
|
|
|
|
const bearerPrefix = "Bearer "
|
|
|
|
|
|
if !strings.HasPrefix(authHeader, bearerPrefix) {
|
|
|
|
|
|
m.respondUnauthorized(c, "认证头部格式无效")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 提取token
|
|
|
|
|
|
tokenString := authHeader[len(bearerPrefix):]
|
|
|
|
|
|
if tokenString == "" {
|
|
|
|
|
|
m.respondUnauthorized(c, "缺少认证令牌")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证token
|
|
|
|
|
|
claims, err := m.jwtAuth.validateToken(tokenString)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
m.logger.Warn("无效的认证令牌",
|
|
|
|
|
|
zap.Error(err),
|
|
|
|
|
|
zap.String("request_id", c.GetString("request_id")))
|
|
|
|
|
|
m.respondUnauthorized(c, "认证令牌无效")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查用户类型是否为管理员
|
|
|
|
|
|
if claims.UserType != "admin" {
|
|
|
|
|
|
m.respondForbidden(c, "需要管理员权限")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 设置用户信息到上下文
|
|
|
|
|
|
c.Set("user_id", claims.UserID)
|
|
|
|
|
|
c.Set("username", claims.Username)
|
|
|
|
|
|
c.Set("email", claims.Email)
|
|
|
|
|
|
c.Set("phone", claims.Phone)
|
|
|
|
|
|
c.Set("user_type", claims.UserType)
|
|
|
|
|
|
c.Set("token_claims", claims)
|
|
|
|
|
|
|
|
|
|
|
|
c.Next()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// IsGlobal 是否为全局中间件
|
|
|
|
|
|
func (m *AdminAuthMiddleware) IsGlobal() bool {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// respondForbidden 返回禁止访问响应
|
|
|
|
|
|
func (m *AdminAuthMiddleware) respondForbidden(c *gin.Context, message string) {
|
|
|
|
|
|
c.JSON(http.StatusForbidden, gin.H{
|
|
|
|
|
|
"success": false,
|
|
|
|
|
|
"message": "权限不足",
|
|
|
|
|
|
"error": message,
|
|
|
|
|
|
"request_id": c.GetString("request_id"),
|
|
|
|
|
|
"timestamp": time.Now().Unix(),
|
|
|
|
|
|
})
|
|
|
|
|
|
c.Abort()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// respondUnauthorized 返回未授权响应
|
|
|
|
|
|
func (m *AdminAuthMiddleware) respondUnauthorized(c *gin.Context, message string) {
|
|
|
|
|
|
c.JSON(http.StatusUnauthorized, gin.H{
|
|
|
|
|
|
"success": false,
|
|
|
|
|
|
"message": "认证失败",
|
|
|
|
|
|
"error": message,
|
|
|
|
|
|
"request_id": c.GetString("request_id"),
|
|
|
|
|
|
"timestamp": time.Now().Unix(),
|
|
|
|
|
|
})
|
|
|
|
|
|
c.Abort()
|
|
|
|
|
|
}
|