|
|
||
|---|---|---|
| .. | ||
| domain | ||
| mock | ||
| repository | ||
| usecase | ||
| README.md | ||
README.md
Permission Module
一個完整的 Go 權限管理模組,提供 JWT 令牌管理、RBAC(基於角色的訪問控制)以及權限樹管理功能。
📋 目錄
🎯 功能特性
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. 安裝依賴
go get backend/pkg/permission
2. 配置
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
// 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
// 權限 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
生成令牌
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
刷新令牌
req := entity.RefreshTokenReq{
RefreshToken: "old-refresh-token",
DeviceID: "device123",
}
tokenResp, err := tokenUC.RefreshToken(ctx, req)
驗證令牌
isValid := tokenUC.IsAccessTokenValid(ctx, accessToken)
取消令牌
err := tokenUC.CancelToken(ctx, tokenID)
黑名單令牌
err := tokenUC.BlacklistToken(ctx, entity.BlacklistTokenReq{
TokenID: tokenID,
Reason: "User logout",
})
權限管理 API
獲取所有權限
permissions, err := permUC.GetAll(ctx)
獲取權限樹
tree, err := permUC.GetTree(ctx)
根據 HTTP 路徑獲取權限
perm, err := permUC.GetByHTTP(ctx, "/api/users", "GET")
展開權限(包含父權限)
perms := permission.Permissions{
"user.list": permission.Open,
}
expanded, err := permUC.ExpandPermissions(ctx, perms)
// expanded 將包含 "user" 和 "user.list"
角色管理 API
創建角色
req := usecase.CreateRoleRequest{
ClientID: 1,
Name: "管理員",
Permissions: permission.Permissions{
"user.list": permission.Open,
"user.create": permission.Open,
},
}
role, err := roleUC.Create(ctx, req)
更新角色
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)
刪除角色
err := roleUC.Delete(ctx, "ROLE0000000001")
分頁查詢角色
filter := usecase.RoleFilterRequest{
ClientID: 1,
Name: "管理",
}
page, err := roleUC.Page(ctx, filter, 1, 10)
// page.List - 角色列表(含使用者數量)
// page.Total - 總數
角色權限管理 API
獲取角色權限
perms, err := rolePermUC.GetByRoleUID(ctx, "ROLE0000000001")
獲取使用者權限
userPerms, err := rolePermUC.GetByUserUID(ctx, "user123")
// userPerms.RoleUID
// userPerms.RoleName
// userPerms.Permissions
更新角色權限
perms := permission.Permissions{
"user.list": permission.Open,
"user.create": permission.Open,
}
err := rolePermUC.UpdateRolePermissions(ctx, "ROLE0000000001", perms)
檢查權限
result, err := rolePermUC.CheckPermission(ctx, "ROLE0000000001", "/api/users", "GET")
// result.Allowed - 是否允許
// result.PermissionName - 權限名稱
// result.PlainCode - 是否有 plain_code 權限
使用者角色管理 API
指派角色給使用者
req := usecase.AssignRoleRequest{
UserUID: "user123",
RoleUID: "ROLE0000000001",
Brand: "brand1",
}
userRole, err := userRoleUC.Assign(ctx, req)
更新使用者角色
userRole, err := userRoleUC.Update(ctx, "user123", "ROLE0000000002")
移除使用者角色
err := userRoleUC.Remove(ctx, "user123")
獲取角色的所有使用者
userRoles, err := userRoleUC.GetByRole(ctx, "ROLE0000000001")
⚙️ 配置說明
Token 配置
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 配置
role:
admin_role_uid: "ADMIN" # 管理員角色 UID
uid_prefix: "ROLE" # 角色 UID 前綴
uid_length: 10 # UID 數字長度
RBAC 配置
rbac:
enable_cache: true # 是否啟用快取
cache_ttl: 3600 # 快取過期時間(秒)
🧪 測試
運行所有測試
go test ./pkg/permission/... -v
運行特定測試
# 令牌測試
go test ./pkg/permission/usecase -run TestToken -v
# 權限樹測試
go test ./pkg/permission/usecase -run TestPermissionTree -v
# Repository 測試
go test ./pkg/permission/repository -v
測試覆蓋率
go test ./pkg/permission/... -cover
生成覆蓋率報告
go test ./pkg/permission/... -coverprofile=coverage.out
go tool cover -html=coverage.out -o coverage.html
💡 最佳實踐
1. 令牌管理
✅ 推薦做法
// 使用 Refresh Token 自動刷新
if tokenUC.IsAccessTokenValid(ctx, accessToken) {
// 使用令牌
} else {
// 嘗試刷新
newToken, err := tokenUC.RefreshToken(ctx, refreshReq)
if err != nil {
// 要求重新登錄
}
}
❌ 避免做法
// 不要在每次請求都生成新令牌
// 不要將令牌存儲在不安全的地方
// 不要在客戶端解析 Refresh Token
2. 權限檢查
✅ 推薦做法
// 使用權限檢查中間件
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()
}
}
❌ 避免做法
// 不要在每個 Handler 中重複權限檢查代碼
// 不要硬編碼權限名稱
// 不要跳過權限檢查
3. 權限設計
✅ 推薦做法
// 使用層級式權限結構
// 例如:
// - user
// - user.list
// - user.create
// - user.update
// - user.delete
// 父權限會自動包含,只需設置子權限即可
perms := permission.Permissions{
"user.list": permission.Open,
}
// 展開後會自動包含 "user"
❌ 避免做法
// 不要創建過深的權限層級(建議 ≤ 3 層)
// 不要使用過於細緻的權限粒度
// 不要創建循環依賴的權限
4. 角色管理
✅ 推薦做法
// 使用預定義角色
const (
RoleAdmin = "ADMIN"
RoleManager = "MANAGER"
RoleEmployee = "EMPLOYEE"
)
// 定期檢查無使用者的角色
roles, _ := roleUC.Page(ctx, filter, 1, 100)
for _, role := range roles.List {
if role.UserCount == 0 {
// 考慮刪除或停用
}
}
❌ 避免做法
// 不要創建過多的角色
// 不要刪除有使用者的角色
// 不要頻繁修改角色權限
🔐 安全建議
-
JWT 密鑰管理
- 使用強密鑰(至少 32 字元)
- 定期輪換密鑰
- 不要將密鑰硬編碼在代碼中
-
令牌過期設置
- Access Token: 15 分鐘 - 1 小時
- Refresh Token: 7 - 30 天
- One-Time Token: 5 - 10 分鐘
-
設備追蹤
- 啟用設備指紋追蹤
- 限制每個設備的令牌數量
- 檢測異常登錄行為
-
權限檢查
- 在所有 API 端點進行權限檢查
- 使用白名單而非黑名單
- 記錄權限拒絕事件
📝 資料庫設計
MongoDB Collections
permissions
{
"_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
{
"_id": ObjectId,
"client_id": 1,
"uid": "ROLE0000000001",
"name": "管理員",
"status": 1,
"create_time": 1234567890,
"update_time": 1234567890
}
role_permissions
{
"_id": ObjectId,
"role_id": ObjectId,
"permission_id": ObjectId,
"create_time": 1234567890,
"update_time": 1234567890
}
user_roles
{
"_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