// Copyright (C) 2024 veypi // 2025-03-04 16:08:06 // Distributed under terms of the MIT license. package auth import ( "encoding/json" "fmt" "strings" "time" "github.com/veypi/vbase/cfg" "github.com/veypi/vbase/libs/cache" "github.com/veypi/vbase/libs/jwt" "github.com/veypi/vbase/models" "github.com/veypi/vigo" ) // orgMemberCache 组织成员身份缓存结构 type orgMemberCache struct { IsMember bool `json:"is_member"` RoleCodes []string `json:"role_codes"` } // AuthMiddleware 统一认证中间件 // 1. JWT认证: 解析token,验证有效性,设置用户信息 // 2. 组织上下文: 如果请求包含org_id,验证用户成员身份,设置组织信息 // 使用Redis缓存组织成员身份和角色信息,减少数据库查询 func AuthMiddleware() func(*vigo.X) error { return func(x *vigo.X) error { // === 1. JWT 认证部分 === tokenString := extractToken(x) if tokenString == "" { return vigo.ErrUnauthorized.WithString("missing token") } // 解析token claims, err := jwt.ParseToken(tokenString) if err != nil { if err == jwt.ErrExpiredToken { return vigo.ErrTokenExpired } return vigo.ErrTokenInvalid } // 检查token是否在黑名单中 if cache.IsEnabled() { blacklisted, _ := cache.IsTokenBlacklisted(claims.ID) if blacklisted { return vigo.ErrUnauthorized.WithString("token has been revoked") } } // 将用户信息存入上下文 x.Set("user_id", claims.UserID) x.Set("user_name", claims.Username) x.Set("user_orgs", claims.Orgs) x.Set("token_claims", claims) // === 2. 组织上下文部分 === orgID := x.Request.Header.Get("X-Org-ID") if orgID == "" { orgID = x.Request.URL.Query().Get("org_id") } if orgID == "" { // 没有指定组织,仅完成用户认证 return nil } // 尝试从缓存获取组织成员信息 var roleCodes []string var isMember bool if cache.IsEnabled() { cacheKey := fmt.Sprintf("auth:org_member:%s:%s", claims.UserID, orgID) cachedData, err := cache.Get(cacheKey) if err == nil && cachedData != "" { var cached orgMemberCache if err := json.Unmarshal([]byte(cachedData), &cached); err == nil { isMember = cached.IsMember roleCodes = cached.RoleCodes } } } // 缓存未命中,查询数据库 if roleCodes == nil { // 验证用户是否为组织成员 var member models.OrgMember err := cfg.DB().Where("org_id = ? AND user_id = ? AND status = ?", orgID, claims.UserID, models.MemberStatusActive).First(&member).Error isMember = err == nil if isMember { // 查询用户的角色 cfg.DB().Model(&models.UserRole{}). Joins("JOIN roles ON user_roles.role_id = roles.id"). Where("user_roles.user_id = ? AND user_roles.org_id = ?", claims.UserID, orgID). Pluck("roles.code", &roleCodes) } // 写入缓存 if cache.IsEnabled() { cacheData := orgMemberCache{ IsMember: isMember, RoleCodes: roleCodes, } if data, err := json.Marshal(cacheData); err == nil { cacheKey := fmt.Sprintf("auth:org_member:%s:%s", claims.UserID, orgID) // 缓存5分钟 cache.Set(cacheKey, string(data), 5*time.Minute) } } } if !isMember { return vigo.ErrForbidden.WithString("you are not a member of this organization") } x.Set("org_id", orgID) x.Set("org_roles", roleCodes) return nil } } func extractToken(x *vigo.X) string { auth := x.Request.Header.Get("Authorization") if auth != "" { if len(auth) > 7 && strings.HasPrefix(auth, "Bearer ") { return auth[7:] } } return x.Request.URL.Query().Get("access_token") }