# Permission Module JWT Token 和 Refresh Token 管理模組,提供完整的身份驗證和授權功能。 ## 📋 功能特性 ### 🔐 JWT Token 管理 - **Access Token 生成**: 基於 JWT 標準生成存取權杖 - **Refresh Token 機制**: 支援長期有效的刷新權杖 - **One-Time Token**: 臨時性權杖,用於特殊場景 - **Token 驗證**: 完整的權杖驗證和解析功能 ### 🚫 黑名單機制 - **即時撤銷**: 將 JWT 權杖立即加入黑名單 - **用戶登出**: 支援單一設備或全設備登出 - **自動過期**: 黑名單條目會在權杖過期後自動清理 - **批量管理**: 支援批量黑名單操作 ### 💾 Redis 儲存 - **高效能**: 使用 Redis 作為主要儲存引擎 - **TTL 管理**: 自動管理權杖過期時間 - **關聯管理**: 支援用戶、設備與權杖的關聯查詢 ### 🔒 安全特性 - **HMAC-SHA256**: 使用安全的簽名算法 - **密鑰分離**: Access Token 和 Refresh Token 使用不同密鑰 - **設備限制**: 支援每用戶、每設備的權杖數量限制 - **過期控制**: 靈活的權杖過期時間配置 ## 🏗️ 架構設計 本模組遵循 **Clean Architecture** 原則: ``` pkg/permission/ ├── domain/ # 領域層 │ ├── entity/ # 實體定義 │ ├── repository/ # 儲存庫介面 │ ├── usecase/ # 用例介面 │ └── token/ # 權杖相關常數和類型 ├── usecase/ # 用例實現 ├── repository/ # 儲存庫實現 └── mock/ # 測試模擬 ``` ### 領域層 (Domain) - **Entity**: 定義核心業務實體(Token、BlacklistEntry、Ticket) - **Repository Interface**: 定義資料存取介面 - **UseCase Interface**: 定義業務用例介面 - **Token Types**: 權杖類型和常數定義 ### 用例層 (UseCase) - **TokenUseCase**: 核心業務邏輯實現 - **JWT 處理**: 權杖生成、解析、驗證 - **黑名單管理**: 權杖撤銷和黑名單查詢 ### 儲存層 (Repository) - **Redis 實現**: 基於 Redis 的資料存取 - **關聯管理**: 用戶、設備、權杖關聯 - **TTL 管理**: 自動過期處理 ## 🚀 快速開始 ### 1. 配置設定 在 `internal/config/config.go` 中添加 Token 配置: ```go type Config struct { // ... 其他配置 Token struct { AccessSecret string // Access Token 簽名密鑰 RefreshSecret string // Refresh Token 簽名密鑰 AccessTokenExpiry time.Duration // Access Token 過期時間 RefreshTokenExpiry time.Duration // Refresh Token 過期時間 OneTimeTokenExpiry time.Duration // 一次性 Token 過期時間 MaxTokensPerUser int // 每用戶最大 Token 數 MaxTokensPerDevice int // 每設備最大 Token 數 } } ``` ### 2. 初始化模組 ```go import ( "backend/pkg/permission/repository" "backend/pkg/permission/usecase" ) // 初始化 Repository tokenRepo := repository.MustTokenRepository(repository.TokenRepositoryParam{ Redis: redisClient, }) // 初始化 UseCase tokenUseCase := usecase.MustTokenUseCase(usecase.TokenUseCaseParam{ TokenRepo: tokenRepo, Config: config, }) ``` ### 3. 基本使用 #### 創建 Access Token ```go req := entity.AuthorizationReq{ GrantType: token.PasswordCredentials.ToString(), Scope: "read write", DeviceID: "device123", IsRefreshToken: true, Claims: map[string]string{ "uid": "user123", "role": "admin", }, } resp, err := tokenUseCase.NewToken(ctx, req) if err != nil { log.Fatal(err) } fmt.Printf("Access Token: %s\n", resp.AccessToken) fmt.Printf("Refresh Token: %s\n", resp.RefreshToken) ``` #### 驗證 Token ```go req := entity.ValidationTokenReq{ Token: accessToken, } resp, err := tokenUseCase.ValidationToken(ctx, req) if err != nil { log.Printf("Token validation failed: %v", err) return } fmt.Printf("Token is valid for user: %s\n", resp.Token.UID) ``` #### 撤銷 Token (加入黑名單) ```go err := tokenUseCase.BlacklistToken(ctx, accessToken, "user logout") if err != nil { log.Printf("Failed to blacklist token: %v", err) } ``` #### 檢查黑名單 ```go isBlacklisted, err := tokenUseCase.IsTokenBlacklisted(ctx, jti) if err != nil { log.Printf("Failed to check blacklist: %v", err) } if isBlacklisted { log.Println("Token is blacklisted") } ``` ## 🧪 測試 ### 運行測試 ```bash # 運行所有測試 go test ./pkg/permission/... # 運行特定模組測試 go test ./pkg/permission/usecase/ go test ./pkg/permission/repository/ # 運行測試並顯示覆蓋率 go test -cover ./pkg/permission/... # 生成覆蓋率報告 go test -coverprofile=coverage.out ./pkg/permission/... go tool cover -html=coverage.out ``` ### 測試結構 - **UseCase Tests**: 業務邏輯測試,使用 Mock Repository - **Repository Tests**: 資料存取測試,使用 MiniRedis - **JWT Tests**: 權杖生成和解析測試 - **Integration Tests**: 整合測試 ## 📊 API 參考 ### TokenUseCase 介面 ```go type TokenUseCase interface { // 基本 Token 操作 NewToken(ctx context.Context, req entity.AuthorizationReq) (entity.TokenResp, error) RefreshToken(ctx context.Context, req entity.RefreshTokenReq) (entity.RefreshTokenResp, error) ValidationToken(ctx context.Context, req entity.ValidationTokenReq) (entity.ValidationTokenResp, error) // Token 管理 CancelToken(ctx context.Context, req entity.CancelTokenReq) error CancelTokens(ctx context.Context, req entity.DoTokenByUIDReq) error CancelTokenByDeviceID(ctx context.Context, req entity.DoTokenByDeviceIDReq) error // 查詢操作 GetUserTokensByUID(ctx context.Context, req entity.QueryTokenByUIDReq) ([]*entity.TokenResp, error) GetUserTokensByDeviceID(ctx context.Context, req entity.DoTokenByDeviceIDReq) ([]*entity.TokenResp, error) // 一次性 Token NewOneTimeToken(ctx context.Context, req entity.CreateOneTimeTokenReq) (entity.CreateOneTimeTokenResp, error) CancelOneTimeToken(ctx context.Context, req entity.CancelOneTimeTokenReq) error // 黑名單操作 BlacklistToken(ctx context.Context, token string, reason string) error IsTokenBlacklisted(ctx context.Context, jti string) (bool, error) BlacklistAllUserTokens(ctx context.Context, uid string, reason string) error // 工具方法 ReadTokenBasicData(ctx context.Context, token string) (map[string]string, error) } ``` ### 主要實體 #### Token 實體 ```go type Token struct { ID string // 權杖唯一標識 UID string // 用戶 ID DeviceID string // 設備 ID AccessToken string // Access Token RefreshToken string // Refresh Token ExpiresIn int // 過期時間(秒) AccessCreateAt time.Time // Access Token 創建時間 RefreshCreateAt time.Time // Refresh Token 創建時間 RefreshExpiresIn int // Refresh Token 過期時間(秒) } ``` #### 黑名單實體 ```go type BlacklistEntry struct { JTI string // JWT ID UID string // 用戶 ID TokenID string // Token ID Reason string // 加入黑名單原因 ExpiresAt int64 // 原始權杖過期時間 CreatedAt int64 // 加入黑名單時間 } ``` ## 🔧 配置參數 | 參數 | 類型 | 說明 | 預設值 | |------|------|------|--------| | `AccessSecret` | string | Access Token 簽名密鑰 | 必填 | | `RefreshSecret` | string | Refresh Token 簽名密鑰 | 必填 | | `AccessTokenExpiry` | Duration | Access Token 過期時間 | 15分鐘 | | `RefreshTokenExpiry` | Duration | Refresh Token 過期時間 | 7天 | | `OneTimeTokenExpiry` | Duration | 一次性 Token 過期時間 | 5分鐘 | | `MaxTokensPerUser` | int | 每用戶最大 Token 數 | 10 | | `MaxTokensPerDevice` | int | 每設備最大 Token 數 | 5 | ## 🚨 錯誤處理 模組定義了完整的錯誤類型: ```go // Token 驗證錯誤 var ( ErrInvalidTokenID = errors.New("invalid token ID") ErrInvalidUID = errors.New("invalid UID") ErrTokenExpired = errors.New("token expired") ErrTokenNotFound = errors.New("token not found") ) // JWT 特定錯誤 var ( ErrInvalidJWTToken = errors.New("invalid JWT token") ErrJWTSigningFailed = errors.New("JWT signing failed") ErrJWTParsingFailed = errors.New("JWT parsing failed") ) // 黑名單錯誤 var ( ErrTokenBlacklisted = errors.New("token is blacklisted") ErrBlacklistNotFound = errors.New("blacklist entry not found") ) ``` ## 🔒 安全考量 ### 1. 密鑰管理 - 使用強密鑰(至少 256 位) - Access Token 和 Refresh Token 使用不同密鑰 - 定期輪換密鑰 ### 2. 權杖過期 - Access Token 使用較短過期時間(15分鐘) - Refresh Token 使用較長過期時間(7天) - 支援自定義過期時間 ### 3. 黑名單機制 - 即時撤銷可疑權杖 - 支援批量撤銷 - 自動清理過期條目 ### 4. 限制機制 - 每用戶權杖數量限制 - 每設備權杖數量限制 - 防止權杖濫用 ## 📈 效能優化 ### 1. Redis 優化 - 使用適當的 TTL 避免記憶體洩漏 - 批量操作減少網路往返 - 使用 Pipeline 提升效能 ### 2. JWT 優化 - 最小化 Claims 數據大小 - 使用高效的序列化格式 - 快取常用的解析結果 ### 3. 黑名單優化 - 使用 SCAN 而非 KEYS 遍歷 - 批量檢查黑名單狀態 - 定期清理過期條目 ## 🤝 貢獻指南 1. Fork 本專案 2. 創建功能分支 (`git checkout -b feature/amazing-feature`) 3. 提交變更 (`git commit -m 'Add some amazing feature'`) 4. 推送到分支 (`git push origin feature/amazing-feature`) 5. 開啟 Pull Request ### 開發規範 - 遵循 Go 編碼規範 - 保持測試覆蓋率 > 80% - 添加適當的文檔註釋 - 使用有意義的提交訊息 ## 📄 授權條款 本專案採用 MIT 授權條款 - 詳見 [LICENSE](LICENSE) 檔案 ## 📞 聯絡資訊 如有問題或建議,請通過以下方式聯絡: - 開啟 Issue - 發送 Pull Request - 聯絡維護團隊 --- **注意**: 本模組是 PlayOne Backend 專案的一部分,請確保與整體架構保持一致。