Redis實現(xiàn)RBAC權(quán)限管理
1. 什么是 RBAC?
RBAC(Role-Based Access Control,基于角色的訪問控制)是一種常見的權(quán)限管理模型,它通過用戶(User)、角色(Role)、權(quán)限(Permission) 及其映射關(guān)系來控制訪問權(quán)限。RBAC 的基本思路是:
- 用戶被分配一個或多個角色;
- 每個角色擁有一定的權(quán)限;
- 通過用戶所屬角色來決定其是否有權(quán)限訪問某個資源。
2. 為什么使用 Redis 實現(xiàn) RBAC?
在傳統(tǒng)的 RBAC 設(shè)計中,權(quán)限數(shù)據(jù)通常存儲在 數(shù)據(jù)庫(如 MySQL),但這種方式可能存在以下問題:
- 查詢性能低:每次鑒權(quán)都需要查詢多張表,影響 API 響應(yīng)速度;
- 不適用于高并發(fā):數(shù)據(jù)庫連接池有限,在高并發(fā)場景下可能成為瓶頸;
- 權(quán)限變更不靈活:數(shù)據(jù)庫方案通常需要定期同步緩存,否則變更不會立即生效。
使用 Redis 作為 RBAC 權(quán)限存儲的優(yōu)勢:
- 高性能:Redis 作為內(nèi)存數(shù)據(jù)庫,查詢速度極快;
- 低延遲:可以直接
O(1)
查詢權(quán)限數(shù)據(jù),而無需復雜的 SQL 語句; - 支持動態(tài)權(quán)限變更:用戶權(quán)限變更可以實時生效,而不需要等待數(shù)據(jù)庫更新;
- 適用于分布式系統(tǒng):多臺服務(wù)器可以共享 Redis 權(quán)限數(shù)據(jù),避免不同實例狀態(tài)不一致的問題。
3. 設(shè)計 RBAC 數(shù)據(jù)結(jié)構(gòu)
我們使用 Redis 作為權(quán)限存儲,并設(shè)計以下 Key 結(jié)構(gòu):
Key | Value | 說明 |
---|---|---|
user_roles:{user_id} | ["admin", "editor"] | 用戶的角色列表 |
role_permissions:{role} | ["read", "write", "delete"] | 角色的權(quán)限列表 |
permission_routes:{permission} | ["GET:/users", "POST:/articles"] | 權(quán)限對應(yīng)的 API |
blacklist_tokens | 存儲已注銷的 Token | 使 JWT 失效,支持主動登出 |
4. 代碼實現(xiàn)
我們使用 Gin 作為 Web 框架,并結(jié)合 Redis 進行權(quán)限管理。
?? 4.1 安裝依賴
go get -u github.com/gin-gonic/gin go get -u github.com/golang-jwt/jwt/v5 go get -u github.com/redis/go-redis/v9
?? 4.2 初始化 Redis
package main import ( "context" "fmt" "log" "github.com/redis/go-redis/v9" ) // 初始化 Redis 客戶端 var ctx = context.Background() var redisClient = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", // 連接 Redis }) // 初始化 RBAC 角色 & 權(quán)限映射 func setupRBAC() { // 角色 → 權(quán)限 redisClient.SAdd(ctx, "role_permissions:admin", "read", "write", "delete") redisClient.SAdd(ctx, "role_permissions:editor", "read", "write") redisClient.SAdd(ctx, "role_permissions:viewer", "read") // 權(quán)限 → API redisClient.SAdd(ctx, "permission_routes:read", "GET:/users", "GET:/articles") redisClient.SAdd(ctx, "permission_routes:write", "POST:/articles", "PUT:/articles") redisClient.SAdd(ctx, "permission_routes:delete", "DELETE:/articles") // 用戶 → 角色 redisClient.SAdd(ctx, "user_roles:1", "admin") redisClient.SAdd(ctx, "user_roles:2", "editor") redisClient.SAdd(ctx, "user_roles:3", "viewer") log.Println("RBAC 權(quán)限映射初始化完成") }
?? 4.3 生成 JWT 令牌
package main import ( "fmt" "time" "github.com/golang-jwt/jwt/v5" ) // JWT 密鑰 var jwtSecret = []byte("supersecretkey") // 生成 JWT 令牌 func GenerateJWT(userID int) (string, error) { token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "user_id": userID, "exp": time.Now().Add(24 * time.Hour).Unix(), // 24 小時有效 }) return token.SignedString(jwtSecret) } // 解析 JWT 令牌 func ParseJWT(tokenString string) (int, error) { token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { return jwtSecret, nil }) if err != nil || !token.Valid { return 0, fmt.Errorf("invalid token") } claims, ok := token.Claims.(jwt.MapClaims) if !ok { return 0, fmt.Errorf("invalid claims") } return int(claims["user_id"].(float64)), nil }
?? 4.4 鑒權(quán)中間件
// 訪問權(quán)限檢查 func hasAccess(userID int, method, path string) bool { // 1. 獲取用戶角色 roles, err := redisClient.SMembers(ctx, fmt.Sprintf("user_roles:%d", userID)).Result() if err != nil || len(roles) == 0 { return false } // 2. 遍歷角色,獲取權(quán)限 for _, role := range roles { permissions, _ := redisClient.SMembers(ctx, fmt.Sprintf("role_permissions:%s", role)).Result() for _, permission := range permissions { routes, _ := redisClient.SMembers(ctx, fmt.Sprintf("permission_routes:%s", permission)).Result() for _, route := range routes { if route == fmt.Sprintf("%s:%s", method, path) { return true } } } } return false } // RBAC 中間件 func RBACMiddleware() gin.HandlerFunc { return func(c *gin.Context) { tokenString := c.GetHeader("Authorization") if tokenString == "" { c.JSON(401, gin.H{"error": "未提供 Token"}) c.Abort() return } // 解析 JWT userID, err := ParseJWT(tokenString) if err != nil { c.JSON(401, gin.H{"error": "Token 無效"}) c.Abort() return } // 檢查權(quán)限 if !hasAccess(userID, c.Request.Method, c.FullPath()) { c.JSON(403, gin.H{"error": "無訪問權(quán)限"}) c.Abort() return } c.Set("userID", userID) c.Next() } }
?? 4.5 API 接口
func main() { r := gin.Default() setupRBAC() // 登錄 r.POST("/login", func(c *gin.Context) { userID := 1 // 假設(shè)用戶 1 登錄 token, _ := GenerateJWT(userID) c.JSON(200, gin.H{"token": token}) }) // 受保護 API api := r.Group("/api", RBACMiddleware()) api.GET("/users", func(c *gin.Context) { c.JSON(200, gin.H{"message": "獲取用戶列表"}) }) api.POST("/articles", func(c *gin.Context) { c.JSON(200, gin.H{"message": "創(chuàng)建文章"}) }) api.DELETE("/articles", func(c *gin.Context) { c.JSON(200, gin.H{"message": "刪除文章"}) }) r.Run(":8080") }
5. 方案總結(jié)
? Redis 存權(quán)限(推薦):高效、適用于分布式
? RBAC 權(quán)限映射:角色權(quán)限映射清晰
? JWT 認證:無狀態(tài),適用于 API 認證
這樣,你就能 用 Redis 設(shè)計一套高效的 RBAC 權(quán)限管理,并支持 API 映射!
到此這篇關(guān)于Redis實現(xiàn)RBAC權(quán)限管理的文章就介紹到這了,更多相關(guān)Redis RBAC權(quán)限內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決redis-cli報錯Could not connect to Redis&
這篇文章主要介紹了解決redis-cli報錯Could not connect to Redis at 127.0.0.1:6379: Connection refused,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2025-04-04Python利用redis限制用戶重復刷新帶來的數(shù)據(jù)問題
在網(wǎng)站開發(fā)中,我們經(jīng)常會遇到需要控制用戶重復刷新頁面的情況,本文就來介紹了Python利用redis限制用戶重復刷新帶來的數(shù)據(jù)問題,感興趣的可以了解一下2024-03-03Redis面試必備之緩存設(shè)計規(guī)范與性能優(yōu)化詳解
你是否在使用Redis時,不清楚Redis應(yīng)該遵循的設(shè)計規(guī)范而苦惱,你是否在Redis出現(xiàn)性能問題時,不知道該如何優(yōu)化而發(fā)愁,快跟隨小編一起學習起來吧2024-03-03