feat: 实现RBAC系统 - 完成Tenant/Role/Resource/User模型、路由配置及MySQL连接配置
This commit is contained in:
203
backend/internal/middleware/auth.go
Normal file
203
backend/internal/middleware/auth.go
Normal file
@@ -0,0 +1,203 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// JWTClaims JWT 声明
|
||||
type JWTClaims struct {
|
||||
UserID uint `json:"user_id"`
|
||||
Username string `json:"username"`
|
||||
TenantID uint `json:"tenant_id"`
|
||||
Role string `json:"role"`
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
// AuthMiddleware 认证中间件
|
||||
func Auth(secretKey string) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// 获取 Authorization 头
|
||||
authHeader := c.GetHeader("Authorization")
|
||||
if authHeader == "" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"error": "缺少认证信息",
|
||||
"message": "请提供 Authorization 头"
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// 解析 Bearer Token
|
||||
parts := strings.SplitN(authHeader, " ", 2)
|
||||
if len(parts) != 2 || parts[0] != "Bearer" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"error": "无效的认证格式",
|
||||
"message": "请使用格式:Bearer {token}"
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
tokenString := parts[1]
|
||||
claims := &JWTClaims{}
|
||||
|
||||
// 解析 Token
|
||||
token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
|
||||
// 验证签名算法
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, errors.New("不支持的签名算法")
|
||||
}
|
||||
return []byte(secretKey), nil
|
||||
})
|
||||
|
||||
if err != nil || !token.Valid {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"error": "无效的 Token",
|
||||
"message": err.Error(),
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// 将解析出的用户信息存入上下文
|
||||
c.Set("user_id", claims.UserID)
|
||||
c.Set("username", claims.Username)
|
||||
c.Set("tenant_id", claims.TenantID)
|
||||
c.Set("role", claims.Role)
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateToken 生成 JWT Token
|
||||
func GenerateToken(secretKey string, userID uint, username string, tenantID uint, role string) (string, error) {
|
||||
expTime := time.Now().Add(24 * time.Hour) // 24 小时过期
|
||||
|
||||
claims := JWTClaims{
|
||||
UserID: userID,
|
||||
Username: username,
|
||||
TenantID: tenantID,
|
||||
Role: role,
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
ExpiresAt: jwt.NewNumericDate(expTime),
|
||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||
NotBefore: jwt.NewNumericDate(time.Now()),
|
||||
},
|
||||
}
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
return token.SignedString([]byte(secretKey))
|
||||
}
|
||||
|
||||
// AdminOnly 仅允许管理员访问
|
||||
func AdminOnly() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
role, exists := c.Get("role")
|
||||
if !exists {
|
||||
c.JSON(http.StatusForbidden, gin.H{
|
||||
"error": "未授权",
|
||||
"message": "请先登录"
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
roleStr, ok := role.(string)
|
||||
if !ok {
|
||||
c.JSON(http.StatusForbidden, gin.H{
|
||||
"error": "角色信息无效",
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// 检查是否为管理员角色
|
||||
isAdminRole := roleStr == "admin" || roleStr == "super_admin" || roleStr == "system_admin"
|
||||
if !isAdminRole {
|
||||
c.JSON(http.StatusForbidden, gin.H{
|
||||
"error": "权限不足",
|
||||
"message": "需要管理员权限才能访问此资源"
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// TenantMiddleware 租户中间件(获取当前租户 ID)
|
||||
func TenantMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// 尝试从路径参数获取租户 ID
|
||||
tenantIDStr := c.Param("tenant_id")
|
||||
if tenantIDStr == "" {
|
||||
// 如果没有路径参数,从用户信息中获取
|
||||
if tenantID, exists := c.Get("tenant_id"); exists {
|
||||
c.Set("current_tenant_id", tenantID)
|
||||
}
|
||||
} else {
|
||||
// 尝试解析租户 ID
|
||||
if tenantID, err := strconv.ParseUint(tenantIDStr, 10, 32); err == nil {
|
||||
c.Set("current_tenant_id", uint(tenantID))
|
||||
}
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// PermissionCheck 权限检查中间件
|
||||
func PermissionCheck(requiredPermissions []string) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
permissionSet, exists := c.Get("permissions")
|
||||
if !exists {
|
||||
c.JSON(http.StatusForbidden, gin.H{
|
||||
"error": "未授权",
|
||||
"message": "请先登录"
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
userPermissions, ok := permissionSet.([]string)
|
||||
if !ok {
|
||||
c.JSON(http.StatusForbidden, gin.H{
|
||||
"error": "权限信息无效",
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// 检查是否拥有所有必需权限
|
||||
for _, required := range requiredPermissions {
|
||||
if !contains(userPermissions, required) {
|
||||
c.JSON(http.StatusForbidden, gin.H{
|
||||
"error": "权限不足",
|
||||
"message": "需要权限:",
|
||||
"required": required,
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// contains 检查切片是否包含元素
|
||||
func contains(slice []string, item string) bool {
|
||||
for _, s := range slice {
|
||||
if s == item {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user