Golang中如何创建访问级别列表

Golang中如何创建访问级别列表 大家好,

我正在尝试用Go语言创建一个市场平台,目前源代码已发布在GitHub上:https://github.com/mehrdaddolatkhah/market-place

在这个服务中,我设置了三种角色:管理员、用户和营销人员。

现在我想为这个服务创建一个访问级别列表。我应该怎么做呢?

我目前的策略是在REST包中创建一个中间层,然后检查JWT令牌。我在JWT令牌中存放了用户ID,接着检查用户调用的端点,然后根据该角色查询数据库。如果用户存在并且有权访问该端点的数据,则返回响应。这是一个好的解决方案吗?或者大家通常是如何处理这个问题的?

感谢您的建议。

抱歉我的英语不好。我正在学术环境中开始学习英语,需要一些时间才能变得更好。


更多关于Golang中如何创建访问级别列表的实战教程也可以访问 https://www.itying.com/category-94-b0.html

3 回复

有些人在 Slack Gophers 频道向我推荐了这个库:https://github.com/casbin/casbin 但我不想使用这个库,我在寻找一种方法,人们如何在服务中为不同角色处理访问权限,并且具备一定的安全性。我不希望用户能够访问管理员部分,或者任何其他角色访问与他们无关的某些部分。你们在服务中是如何处理角色之间的这种访问权限的?

更多关于Golang中如何创建访问级别列表的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


我猜您正在开发某种Web应用程序。对于需要角色和访问权限的应用,我使用了Gorilla mux,配合子路由并在中间件中实现身份验证。

在Go中实现访问控制列表(ACL)通常采用中间件结合角色/权限验证的方式。以下是针对您场景的具体实现方案:

1. 定义角色和权限常量

// auth/roles.go
package auth

type Role string

const (
    RoleAdmin      Role = "admin"
    RoleUser       Role = "user"
    RoleMarketer   Role = "marketer"
)

// 权限定义
type Permission string

const (
    PermissionCreateProduct Permission = "product:create"
    PermissionDeleteProduct Permission = "product:delete"
    PermissionViewAnalytics Permission = "analytics:view"
    PermissionManageUsers   Permission = "users:manage"
)

// 角色权限映射
var RolePermissions = map[Role][]Permission{
    RoleAdmin: {
        PermissionCreateProduct,
        PermissionDeleteProduct,
        PermissionViewAnalytics,
        PermissionManageUsers,
    },
    RoleUser: {
        PermissionCreateProduct,
    },
    RoleMarketer: {
        PermissionCreateProduct,
        PermissionViewAnalytics,
    },
}

2. 实现JWT中间件

// middleware/auth.go
package middleware

import (
    "context"
    "net/http"
    "strings"
    "your-project/auth"
    "your-project/models"
)

type contextKey string

const UserContextKey contextKey = "user"

func AuthMiddleware(jwtSecret string) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            authHeader := r.Header.Get("Authorization")
            if authHeader == "" {
                http.Error(w, "Authorization header required", http.StatusUnauthorized)
                return
            }

            tokenString := strings.TrimPrefix(authHeader, "Bearer ")
            claims, err := auth.ValidateToken(tokenString, jwtSecret)
            if err != nil {
                http.Error(w, "Invalid token", http.StatusUnauthorized)
                return
            }

            // 将用户信息存入上下文
            user := &models.User{
                ID:    claims.UserID,
                Role:  auth.Role(claims.Role),
                Email: claims.Email,
            }
            
            ctx := context.WithValue(r.Context(), UserContextKey, user)
            next.ServeHTTP(w, r.WithContext(ctx))
        })
    }
}

3. 实现ACL中间件

// middleware/acl.go
package middleware

import (
    "net/http"
    "your-project/auth"
)

func RequirePermission(permission auth.Permission) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            user, ok := r.Context().Value(UserContextKey).(*models.User)
            if !ok {
                http.Error(w, "User not found in context", http.StatusUnauthorized)
                return
            }

            // 检查用户角色是否拥有所需权限
            permissions, exists := auth.RolePermissions[user.Role]
            if !exists {
                http.Error(w, "Access denied", http.StatusForbidden)
                return
            }

            hasPermission := false
            for _, p := range permissions {
                if p == permission {
                    hasPermission = true
                    break
                }
            }

            if !hasPermission {
                http.Error(w, "Insufficient permissions", http.StatusForbidden)
                return
            }

            next.ServeHTTP(w, r)
        })
    }
}

// 基于角色的快捷方式
func RequireRole(role auth.Role) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            user, ok := r.Context().Value(UserContextKey).(*models.User)
            if !ok {
                http.Error(w, "User not found in context", http.StatusUnauthorized)
                return
            }

            if user.Role != role {
                http.Error(w, "Access denied", http.StatusForbidden)
                return
            }

            next.ServeHTTP(w, r)
        })
    }
}

4. 路由配置示例

// routes/routes.go
package routes

import (
    "net/http"
    "your-project/handlers"
    "your-project/middleware"
    "your-project/auth"
)

func SetupRoutes() http.Handler {
    mux := http.NewServeMux()
    
    // 公开路由
    mux.HandleFunc("/login", handlers.LoginHandler)
    mux.HandleFunc("/register", handlers.RegisterHandler)
    
    // 需要认证的路由
    authMiddleware := middleware.AuthMiddleware("your-jwt-secret")
    
    // 用户路由
    mux.Handle("/products", authMiddleware(
        middleware.RequirePermission(auth.PermissionCreateProduct)(
            http.HandlerFunc(handlers.CreateProductHandler),
        ),
    ))
    
    // 管理员路由
    mux.Handle("/admin/users", authMiddleware(
        middleware.RequireRole(auth.RoleAdmin)(
            http.HandlerFunc(handlers.ManageUsersHandler),
        ),
    ))
    
    // 营销人员路由
    mux.Handle("/analytics", authMiddleware(
        middleware.RequirePermission(auth.PermissionViewAnalytics)(
            http.HandlerFunc(handlers.ViewAnalyticsHandler),
        ),
    ))
    
    return mux
}

5. JWT令牌生成和验证

// auth/jwt.go
package auth

import (
    "time"
    "github.com/golang-jwt/jwt/v4"
)

type Claims struct {
    UserID string `json:"user_id"`
    Role   string `json:"role"`
    Email  string `json:"email"`
    jwt.RegisteredClaims
}

func GenerateToken(userID, role, email, secret string) (string, error) {
    claims := Claims{
        UserID: userID,
        Role:   role,
        Email:  email,
        RegisteredClaims: jwt.RegisteredClaims{
            ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
            IssuedAt:  jwt.NewNumericDate(time.Now()),
        },
    }
    
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    return token.SignedString([]byte(secret))
}

func ValidateToken(tokenString, secret string) (*Claims, error) {
    token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
        return []byte(secret), nil
    })
    
    if err != nil {
        return nil, err
    }
    
    if claims, ok := token.Claims.(*Claims); ok && token.Valid {
        return claims, nil
    }
    
    return nil, jwt.ErrSignatureInvalid
}

6. 数据库查询优化

// repository/user_repo.go
package repository

import (
    "context"
    "database/sql"
    "your-project/models"
)

type UserRepository struct {
    db *sql.DB
}

func (r *UserRepository) GetUserWithPermissions(ctx context.Context, userID string) (*models.UserWithPermissions, error) {
    query := `
        SELECT u.id, u.email, u.role, 
               ARRAY_AGG(p.permission) as permissions
        FROM users u
        LEFT JOIN role_permissions rp ON u.role = rp.role
        LEFT JOIN permissions p ON rp.permission_id = p.id
        WHERE u.id = $1
        GROUP BY u.id, u.email, u.role
    `
    
    var user models.UserWithPermissions
    err := r.db.QueryRowContext(ctx, query, userID).Scan(
        &user.ID,
        &user.Email,
        &user.Role,
        &user.Permissions,
    )
    
    if err != nil {
        return nil, err
    }
    
    return &user, nil
}

这个方案将访问控制逻辑从业务代码中分离出来,通过中间件实现权限验证。JWT中存储用户角色,中间件根据路由需要的权限进行验证,避免了每次请求都查询数据库。对于更复杂的权限系统,可以考虑使用Casbin等成熟的ACL库。

回到顶部