backend/pkg/permission/README.md

364 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 專案的一部分,請確保與整體架構保持一致。