| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111 |
- package middleware
- import (
- "context"
- "errors"
- "strings"
- "github.com/Wei-Shaw/sub2api/internal/service"
- "github.com/gin-gonic/gin"
- )
- // NewJWTAuthMiddleware 创建 JWT 认证中间件
- func NewJWTAuthMiddleware(
- authService *service.AuthService,
- userService *service.UserService,
- settingService *service.SettingService,
- auditService *service.AuditLogService,
- ) JWTAuthMiddleware {
- return JWTAuthMiddleware(jwtAuth(authService, userService, userService, settingService, auditService))
- }
- type jwtUserReader interface {
- GetByID(ctx context.Context, id int64) (*service.User, error)
- }
- type userActivityToucher interface {
- TouchLastActiveForUser(ctx context.Context, user *service.User)
- }
- // jwtAuth JWT认证中间件实现
- func jwtAuth(
- authService *service.AuthService,
- userService jwtUserReader,
- activityToucher userActivityToucher,
- settingService *service.SettingService,
- auditService *service.AuditLogService,
- ) gin.HandlerFunc {
- return func(c *gin.Context) {
- // 从Authorization header中提取token
- authHeader := c.GetHeader("Authorization")
- if authHeader == "" {
- AbortWithError(c, 401, "UNAUTHORIZED", "Authorization header is required")
- return
- }
- // 验证Bearer scheme
- parts := strings.SplitN(authHeader, " ", 2)
- if len(parts) != 2 || !strings.EqualFold(parts[0], "Bearer") {
- AbortWithError(c, 401, "INVALID_AUTH_HEADER", "Authorization header format must be 'Bearer {token}'")
- return
- }
- tokenString := strings.TrimSpace(parts[1])
- if tokenString == "" {
- AbortWithError(c, 401, "EMPTY_TOKEN", "Token cannot be empty")
- return
- }
- // 验证token
- claims, err := authService.ValidateToken(tokenString)
- if err != nil {
- if errors.Is(err, service.ErrTokenExpired) {
- AbortWithError(c, 401, "TOKEN_EXPIRED", "Token has expired")
- return
- }
- AbortWithError(c, 401, "INVALID_TOKEN", "Invalid token")
- return
- }
- // 从数据库获取最新的用户信息
- user, err := userService.GetByID(c.Request.Context(), claims.UserID)
- if err != nil {
- AbortWithError(c, 401, "USER_NOT_FOUND", "User not found")
- return
- }
- // 检查用户状态
- if !user.IsActive() {
- AbortWithError(c, 401, "USER_INACTIVE", "User account is not active")
- return
- }
- // Security: Validate TokenVersion to ensure token hasn't been invalidated
- // This check ensures tokens issued before a password change are rejected
- if claims.TokenVersion != user.TokenVersion {
- AbortWithError(c, 401, "TOKEN_REVOKED", "Token has been revoked (password changed)")
- return
- }
- // 会话绑定校验:IP/UA 任一变化即撤销会话(功能可在系统设置中关闭)
- if !enforceSessionBinding(c, authService, settingService, auditService, claims) {
- return
- }
- c.Set(string(ContextKeyUser), AuthSubject{
- UserID: user.ID,
- Concurrency: user.Concurrency,
- })
- c.Set(string(ContextKeyUserRole), user.Role)
- c.Set(ContextKeyAuthEmail, user.Email)
- c.Set(ContextKeySessionID, claims.SessionID)
- if activityToucher != nil {
- activityToucher.TouchLastActiveForUser(c.Request.Context(), user)
- }
- c.Next()
- }
- }
- // Deprecated: prefer GetAuthSubjectFromContext in auth_subject.go.
|