# Permission Module 一個完整的 Go 權限管理模組,提供 JWT 令牌管理、RBAC(基於角色的訪問控制)以及權限樹管理功能。 ## 📋 目錄 - [功能特性](#功能特性) - [架構設計](#架構設計) - [目錄結構](#目錄結構) - [快速開始](#快速開始) - [API 文檔](#api-文檔) - [配置說明](#配置說明) - [測試](#測試) - [最佳實踐](#最佳實踐) ## 🎯 功能特性 ### 1. JWT 令牌管理 - ✅ Access Token 與 Refresh Token 機制 - ✅ One-Time Token 支持(一次性令牌) - ✅ 設備追蹤與管理 - ✅ 令牌黑名單機制 - ✅ 多設備登錄限制 - ✅ 令牌自動過期與刷新 ### 2. RBAC 權限控制 - ✅ 層級式權限結構(權限樹) - ✅ 角色與權限關聯管理 - ✅ 使用者與角色關聯管理 - ✅ 動態權限檢查 - ✅ HTTP API 權限映射 - ✅ 權限繼承(父權限自動包含) ### 3. 資料持久化 - ✅ Redis 令牌存儲與快取 - ✅ MongoDB 權限/角色數據存儲 - ✅ 批量查詢優化(避免 N+1) - ✅ 軟刪除支持 ### 4. 安全特性 - ✅ JWT 簽名驗證 - ✅ 令牌黑名單 - ✅ 設備指紋追蹤 - ✅ 循環依賴檢測 - ✅ 管理員權限特殊處理 ## 🏗️ 架構設計 本模組遵循 **Clean Architecture** 設計原則: ``` pkg/permission/ ├── domain/ # 領域層(核心業務邏輯) │ ├── entity/ # 實體定義 │ ├── repository/ # Repository 介面 │ ├── usecase/ # UseCase 介面 │ ├── config/ # 配置定義 │ ├── permission/ # 權限類型定義 │ └── token/ # 令牌類型定義 ├── usecase/ # UseCase 實現層 │ ├── token.go # 令牌業務邏輯 │ ├── permission_usecase.go # 權限業務邏輯 │ ├── role_usecase.go # 角色業務邏輯 │ ├── role_permission_usecase.go # 角色權限業務邏輯 │ ├── user_role_usecase.go # 使用者角色業務邏輯 │ └── permission_tree.go # 權限樹結構 ├── repository/ # Repository 實現層(數據訪問) │ ├── token_model.go # Redis 令牌存儲 │ ├── permission.go # MongoDB 權限存儲 │ ├── role.go # MongoDB 角色存儲 │ ├── role_permission.go # MongoDB 角色權限存儲 │ └── user_role.go # MongoDB 使用者角色存儲 └── mock/ # Mock 實現(測試用) ``` ### 依賴關係圖 ``` ┌─────────────────┐ │ HTTP Handler │ └────────┬────────┘ │ ▼ ┌─────────────────┐ │ UseCase │ ◄─── 業務邏輯層 │ - Token │ │ - Permission │ │ - Role │ │ - UserRole │ └────────┬────────┘ │ ▼ ┌─────────────────┐ │ Repository │ ◄─── 數據訪問層 │ - Redis │ │ - MongoDB │ └────────┬────────┘ │ ▼ ┌─────────────────┐ │ Storage │ ◄─── 存儲層 │ - Redis │ │ - MongoDB │ └─────────────────┘ ``` ## 📁 目錄結構 ### domain/ - 領域層 #### entity/ - 實體定義 - `token.go` - 令牌實體(Token, Ticket, Blacklist) - `permission.go` - 權限實體 - `role.go` - 角色實體 - `role_permission.go` - 角色權限關聯實體 - `user_role.go` - 使用者角色實體 #### repository/ - Repository 介面 定義數據訪問接口,與具體實現解耦 #### usecase/ - UseCase 介面 定義業務邏輯接口,規範業務操作 #### config/ - 配置定義 - `config.go` - 模組配置結構 - `errors.go` - 配置錯誤定義 #### permission/ - 權限類型 - `types.go` - 權限狀態、類型、集合定義 #### token/ - 令牌類型 - `token_type.go` - 令牌類型常量 - `grant_type.go` - 授權類型定義 ### usecase/ - 業務邏輯實現 #### 令牌管理 - `token.go` - JWT 令牌生成、驗證、刷新 - `token_jwt.go` - JWT 編解碼 - `token_claims.go` - JWT Claims 處理 #### RBAC 管理 - `permission_usecase.go` - 權限管理 - `role_usecase.go` - 角色管理 - `role_permission_usecase.go` - 角色權限管理 - `user_role_usecase.go` - 使用者角色管理 - `permission_tree.go` - 權限樹結構與操作 ### repository/ - 數據訪問實現 - `token_model.go` - Redis 令牌存儲實現 - `permission.go` - MongoDB 權限存儲實現 - `role.go` - MongoDB 角色存儲實現 - `role_permission.go` - MongoDB 角色權限存儲實現 - `user_role.go` - MongoDB 使用者角色存儲實現 ### mock/ - Mock 實現 自動生成的 Mock 實現,用於單元測試 ## 🚀 快速開始 ### 1. 安裝依賴 ```bash go get backend/pkg/permission ``` ### 2. 配置 ```go import ( "backend/pkg/permission/domain/config" "backend/pkg/permission/usecase" "backend/pkg/permission/repository" ) // 配置 cfg := &config.Config{ Token: config.TokenConfig{ Secret: "your-secret-key", Expired: config.ExpiredConfig{ Seconds: 900, // 15 分鐘 }, RefreshExpires: config.ExpiredConfig{ Seconds: 604800, // 7 天 }, Issuer: "playone-backend", MaxTokensPerUser: 10, MaxTokensPerDevice: 5, }, Role: config.RoleConfig{ AdminRoleUID: "ADMIN", UIDPrefix: "ROLE", UIDLength: 10, }, } ``` ### 3. 初始化 Repository ```go // Redis 令牌存儲 redisClient := redis.NewClient(&redis.Options{ Addr: "localhost:6379", }) tokenRepo := repository.NewTokenRepository(repository.TokenRepositoryParam{ Redis: redisClient, }) // MongoDB 存儲 mongoConf := &mongo.Conf{ Host: "localhost:27017", Database: "permission", User: "admin", Password: "password", } permRepo := repository.NewPermissionRepository(repository.PermissionRepositoryParam{ Conf: mongoConf, CacheConf: cache.CacheConf{}, }) roleRepo := repository.NewRoleRepository(repository.RoleRepositoryParam{ Conf: mongoConf, CacheConf: cache.CacheConf{}, }) rolePermRepo := repository.NewRolePermissionRepository(repository.RolePermissionRepositoryParam{ Conf: mongoConf, CacheConf: cache.CacheConf{}, }) userRoleRepo := repository.NewUserRoleRepository(repository.UserRoleRepositoryParam{ Conf: mongoConf, CacheConf: cache.CacheConf{}, }) ``` ### 4. 初始化 UseCase ```go // 權限 UseCase permUC := usecase.NewPermissionUseCase(usecase.PermissionUseCaseParam{ PermRepo: permRepo, RolePermRepo: rolePermRepo, RoleRepo: roleRepo, UserRoleRepo: userRoleRepo, }) // 角色權限 UseCase rolePermUC := usecase.NewRolePermissionUseCase(usecase.RolePermissionUseCaseParam{ PermRepo: permRepo, RolePermRepo: rolePermRepo, RoleRepo: roleRepo, UserRoleRepo: userRoleRepo, PermUseCase: permUC, AdminRoleUID: cfg.Role.AdminRoleUID, }) // 角色 UseCase roleUC := usecase.NewRoleUseCase(usecase.RoleUseCaseParam{ RoleRepo: roleRepo, UserRoleRepo: userRoleRepo, RolePermUseCase: rolePermUC, Config: usecase.RoleUseCaseConfig{ AdminRoleUID: cfg.Role.AdminRoleUID, UIDPrefix: cfg.Role.UIDPrefix, UIDLength: cfg.Role.UIDLength, }, }) // 使用者角色 UseCase userRoleUC := usecase.NewUserRoleUseCase(usecase.UserRoleUseCaseParam{ UserRoleRepo: userRoleRepo, RoleRepo: roleRepo, }) // 令牌 UseCase tokenUC := usecase.NewTokenUseCase(usecase.TokenUseCaseParam{ TokenRepo: tokenRepo, Config: cfg, }) ``` ## 📚 API 文檔 ### 令牌管理 API #### 生成令牌 ```go req := entity.AuthorizationReq{ GrantType: token.PasswordCredentials.ToString(), Data: map[string]string{ "uid": "user123", "role": "admin", }, DeviceID: "device123", } tokenResp, err := tokenUC.NewToken(ctx, req) // tokenResp.AccessToken // tokenResp.RefreshToken // tokenResp.ExpiresIn ``` #### 刷新令牌 ```go req := entity.RefreshTokenReq{ RefreshToken: "old-refresh-token", DeviceID: "device123", } tokenResp, err := tokenUC.RefreshToken(ctx, req) ``` #### 驗證令牌 ```go isValid := tokenUC.IsAccessTokenValid(ctx, accessToken) ``` #### 取消令牌 ```go err := tokenUC.CancelToken(ctx, tokenID) ``` #### 黑名單令牌 ```go err := tokenUC.BlacklistToken(ctx, entity.BlacklistTokenReq{ TokenID: tokenID, Reason: "User logout", }) ``` ### 權限管理 API #### 獲取所有權限 ```go permissions, err := permUC.GetAll(ctx) ``` #### 獲取權限樹 ```go tree, err := permUC.GetTree(ctx) ``` #### 根據 HTTP 路徑獲取權限 ```go perm, err := permUC.GetByHTTP(ctx, "/api/users", "GET") ``` #### 展開權限(包含父權限) ```go perms := permission.Permissions{ "user.list": permission.Open, } expanded, err := permUC.ExpandPermissions(ctx, perms) // expanded 將包含 "user" 和 "user.list" ``` ### 角色管理 API #### 創建角色 ```go req := usecase.CreateRoleRequest{ ClientID: 1, Name: "管理員", Permissions: permission.Permissions{ "user.list": permission.Open, "user.create": permission.Open, }, } role, err := roleUC.Create(ctx, req) ``` #### 更新角色 ```go name := "高級管理員" req := usecase.UpdateRoleRequest{ Name: &name, Permissions: permission.Permissions{ "user.list": permission.Open, "user.create": permission.Open, "user.delete": permission.Open, }, } role, err := roleUC.Update(ctx, "ROLE0000000001", req) ``` #### 刪除角色 ```go err := roleUC.Delete(ctx, "ROLE0000000001") ``` #### 分頁查詢角色 ```go filter := usecase.RoleFilterRequest{ ClientID: 1, Name: "管理", } page, err := roleUC.Page(ctx, filter, 1, 10) // page.List - 角色列表(含使用者數量) // page.Total - 總數 ``` ### 角色權限管理 API #### 獲取角色權限 ```go perms, err := rolePermUC.GetByRoleUID(ctx, "ROLE0000000001") ``` #### 獲取使用者權限 ```go userPerms, err := rolePermUC.GetByUserUID(ctx, "user123") // userPerms.RoleUID // userPerms.RoleName // userPerms.Permissions ``` #### 更新角色權限 ```go perms := permission.Permissions{ "user.list": permission.Open, "user.create": permission.Open, } err := rolePermUC.UpdateRolePermissions(ctx, "ROLE0000000001", perms) ``` #### 檢查權限 ```go result, err := rolePermUC.CheckPermission(ctx, "ROLE0000000001", "/api/users", "GET") // result.Allowed - 是否允許 // result.PermissionName - 權限名稱 // result.PlainCode - 是否有 plain_code 權限 ``` ### 使用者角色管理 API #### 指派角色給使用者 ```go req := usecase.AssignRoleRequest{ UserUID: "user123", RoleUID: "ROLE0000000001", Brand: "brand1", } userRole, err := userRoleUC.Assign(ctx, req) ``` #### 更新使用者角色 ```go userRole, err := userRoleUC.Update(ctx, "user123", "ROLE0000000002") ``` #### 移除使用者角色 ```go err := userRoleUC.Remove(ctx, "user123") ``` #### 獲取角色的所有使用者 ```go userRoles, err := userRoleUC.GetByRole(ctx, "ROLE0000000001") ``` ## ⚙️ 配置說明 ### Token 配置 ```yaml token: secret: "your-jwt-secret-key" # JWT 簽名密鑰 expired: seconds: 900 # Access Token 過期時間(秒) refresh_expires: seconds: 604800 # Refresh Token 過期時間(秒) issuer: "playone-backend" # 發行者 max_tokens_per_user: 10 # 每個使用者最大令牌數 max_tokens_per_device: 5 # 每個設備最大令牌數 enable_device_tracking: true # 是否啟用設備追蹤 ``` ### Role 配置 ```yaml role: admin_role_uid: "ADMIN" # 管理員角色 UID uid_prefix: "ROLE" # 角色 UID 前綴 uid_length: 10 # UID 數字長度 ``` ### RBAC 配置 ```yaml rbac: enable_cache: true # 是否啟用快取 cache_ttl: 3600 # 快取過期時間(秒) ``` ## 🧪 測試 ### 運行所有測試 ```bash go test ./pkg/permission/... -v ``` ### 運行特定測試 ```bash # 令牌測試 go test ./pkg/permission/usecase -run TestToken -v # 權限樹測試 go test ./pkg/permission/usecase -run TestPermissionTree -v # Repository 測試 go test ./pkg/permission/repository -v ``` ### 測試覆蓋率 ```bash go test ./pkg/permission/... -cover ``` ### 生成覆蓋率報告 ```bash go test ./pkg/permission/... -coverprofile=coverage.out go tool cover -html=coverage.out -o coverage.html ``` ## 💡 最佳實踐 ### 1. 令牌管理 #### ✅ 推薦做法 ```go // 使用 Refresh Token 自動刷新 if tokenUC.IsAccessTokenValid(ctx, accessToken) { // 使用令牌 } else { // 嘗試刷新 newToken, err := tokenUC.RefreshToken(ctx, refreshReq) if err != nil { // 要求重新登錄 } } ``` #### ❌ 避免做法 ```go // 不要在每次請求都生成新令牌 // 不要將令牌存儲在不安全的地方 // 不要在客戶端解析 Refresh Token ``` ### 2. 權限檢查 #### ✅ 推薦做法 ```go // 使用權限檢查中間件 func AuthMiddleware(rolePermUC usecase.RolePermissionUseCase) gin.HandlerFunc { return func(c *gin.Context) { roleUID := c.GetString("role_uid") result, err := rolePermUC.CheckPermission( c.Request.Context(), roleUID, c.Request.URL.Path, c.Request.Method, ) if err != nil || !result.Allowed { c.AbortWithStatus(http.StatusForbidden) return } c.Next() } } ``` #### ❌ 避免做法 ```go // 不要在每個 Handler 中重複權限檢查代碼 // 不要硬編碼權限名稱 // 不要跳過權限檢查 ``` ### 3. 權限設計 #### ✅ 推薦做法 ```go // 使用層級式權限結構 // 例如: // - user // - user.list // - user.create // - user.update // - user.delete // 父權限會自動包含,只需設置子權限即可 perms := permission.Permissions{ "user.list": permission.Open, } // 展開後會自動包含 "user" ``` #### ❌ 避免做法 ```go // 不要創建過深的權限層級(建議 ≤ 3 層) // 不要使用過於細緻的權限粒度 // 不要創建循環依賴的權限 ``` ### 4. 角色管理 #### ✅ 推薦做法 ```go // 使用預定義角色 const ( RoleAdmin = "ADMIN" RoleManager = "MANAGER" RoleEmployee = "EMPLOYEE" ) // 定期檢查無使用者的角色 roles, _ := roleUC.Page(ctx, filter, 1, 100) for _, role := range roles.List { if role.UserCount == 0 { // 考慮刪除或停用 } } ``` #### ❌ 避免做法 ```go // 不要創建過多的角色 // 不要刪除有使用者的角色 // 不要頻繁修改角色權限 ``` ## 🔐 安全建議 1. **JWT 密鑰管理** - 使用強密鑰(至少 32 字元) - 定期輪換密鑰 - 不要將密鑰硬編碼在代碼中 2. **令牌過期設置** - Access Token: 15 分鐘 - 1 小時 - Refresh Token: 7 - 30 天 - One-Time Token: 5 - 10 分鐘 3. **設備追蹤** - 啟用設備指紋追蹤 - 限制每個設備的令牌數量 - 檢測異常登錄行為 4. **權限檢查** - 在所有 API 端點進行權限檢查 - 使用白名單而非黑名單 - 記錄權限拒絕事件 ## 📝 資料庫設計 ### MongoDB Collections #### permissions ```json { "_id": ObjectId, "parent_id": ObjectId, "name": "user.list", "http_method": "GET", "http_path": "/api/users", "status": 1, "type": 1, "create_time": 1234567890, "update_time": 1234567890 } ``` #### roles ```json { "_id": ObjectId, "client_id": 1, "uid": "ROLE0000000001", "name": "管理員", "status": 1, "create_time": 1234567890, "update_time": 1234567890 } ``` #### role_permissions ```json { "_id": ObjectId, "role_id": ObjectId, "permission_id": ObjectId, "create_time": 1234567890, "update_time": 1234567890 } ``` #### user_roles ```json { "_id": ObjectId, "brand": "brand1", "uid": "user123", "role_id": "ROLE0000000001", "status": 1, "create_time": 1234567890, "update_time": 1234567890 } ``` ### Redis Keys ``` permission:access_token:{token_id} # Access Token permission:refresh_token:{token_id} # Refresh Token permission:device_token:{device_id} # Device Token List permission:uid_token:{uid} # User Token List permission:ticket:{ticket} # One-Time Token permission:blacklist:{token_id} # Token Blacklist ```