backend/pkg/permission/README.md

364 lines
10 KiB
Markdown
Raw Permalink Normal View History

2025-10-06 08:28:39 +00:00
# Permission Module
2025-10-03 08:38:12 +00:00
2025-10-06 08:28:39 +00:00
JWT Token 和 Refresh Token 管理模組,提供完整的身份驗證和授權功能。
2025-10-03 08:38:12 +00:00
2025-10-06 08:28:39 +00:00
## 📋 功能特性
2025-10-03 08:38:12 +00:00
2025-10-06 08:28:39 +00:00
### 🔐 JWT Token 管理
- **Access Token 生成**: 基於 JWT 標準生成存取權杖
- **Refresh Token 機制**: 支援長期有效的刷新權杖
- **One-Time Token**: 臨時性權杖,用於特殊場景
- **Token 驗證**: 完整的權杖驗證和解析功能
2025-10-03 08:38:12 +00:00
2025-10-06 08:28:39 +00:00
### 🚫 黑名單機制
- **即時撤銷**: 將 JWT 權杖立即加入黑名單
- **用戶登出**: 支援單一設備或全設備登出
- **自動過期**: 黑名單條目會在權杖過期後自動清理
- **批量管理**: 支援批量黑名單操作
2025-10-03 08:38:12 +00:00
2025-10-06 08:28:39 +00:00
### 💾 Redis 儲存
- **高效能**: 使用 Redis 作為主要儲存引擎
- **TTL 管理**: 自動管理權杖過期時間
- **關聯管理**: 支援用戶、設備與權杖的關聯查詢
2025-10-03 08:38:12 +00:00
2025-10-06 08:28:39 +00:00
### 🔒 安全特性
- **HMAC-SHA256**: 使用安全的簽名算法
- **密鑰分離**: Access Token 和 Refresh Token 使用不同密鑰
- **設備限制**: 支援每用戶、每設備的權杖數量限制
- **過期控制**: 靈活的權杖過期時間配置
## 🏗️ 架構設計
本模組遵循 **Clean Architecture** 原則:
2025-10-03 08:38:12 +00:00
```
pkg/permission/
2025-10-06 08:28:39 +00:00
├── domain/ # 領域層
│ ├── entity/ # 實體定義
│ ├── repository/ # 儲存庫介面
│ ├── usecase/ # 用例介面
│ └── token/ # 權杖相關常數和類型
├── usecase/ # 用例實現
├── repository/ # 儲存庫實現
└── mock/ # 測試模擬
2025-10-03 08:38:12 +00:00
```
2025-10-06 08:28:39 +00:00
### 領域層 (Domain)
- **Entity**: 定義核心業務實體Token、BlacklistEntry、Ticket
- **Repository Interface**: 定義資料存取介面
- **UseCase Interface**: 定義業務用例介面
- **Token Types**: 權杖類型和常數定義
2025-10-03 08:38:12 +00:00
2025-10-06 08:28:39 +00:00
### 用例層 (UseCase)
- **TokenUseCase**: 核心業務邏輯實現
- **JWT 處理**: 權杖生成、解析、驗證
- **黑名單管理**: 權杖撤銷和黑名單查詢
2025-10-03 08:38:12 +00:00
2025-10-06 08:28:39 +00:00
### 儲存層 (Repository)
- **Redis 實現**: 基於 Redis 的資料存取
- **關聯管理**: 用戶、設備、權杖關聯
- **TTL 管理**: 自動過期處理
2025-10-03 08:38:12 +00:00
2025-10-06 08:28:39 +00:00
## 🚀 快速開始
2025-10-03 08:38:12 +00:00
2025-10-06 08:28:39 +00:00
### 1. 配置設定
2025-10-03 08:38:12 +00:00
2025-10-06 08:28:39 +00:00
`internal/config/config.go` 中添加 Token 配置:
2025-10-03 08:38:12 +00:00
2025-10-06 08:28:39 +00:00
```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 數
}
}
```
2025-10-03 08:38:12 +00:00
2025-10-06 08:28:39 +00:00
### 2. 初始化模組
2025-10-03 08:38:12 +00:00
2025-10-06 08:28:39 +00:00
```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,
})
2025-10-03 08:38:12 +00:00
```
2025-10-06 08:28:39 +00:00
### 3. 基本使用
#### 創建 Access Token
2025-10-03 08:38:12 +00:00
2025-10-06 08:28:39 +00:00
```go
req := entity.AuthorizationReq{
GrantType: token.PasswordCredentials.ToString(),
Scope: "read write",
DeviceID: "device123",
IsRefreshToken: true,
Claims: map[string]string{
"uid": "user123",
"role": "admin",
},
}
2025-10-03 08:38:12 +00:00
2025-10-06 08:28:39 +00:00
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
2025-10-03 08:38:12 +00:00
```go
2025-10-06 08:28:39 +00:00
req := entity.ValidationTokenReq{
Token: accessToken,
2025-10-03 08:38:12 +00:00
}
2025-10-06 08:28:39 +00:00
resp, err := tokenUseCase.ValidationToken(ctx, req)
if err != nil {
log.Printf("Token validation failed: %v", err)
return
}
2025-10-03 08:38:12 +00:00
2025-10-06 08:28:39 +00:00
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)
2025-10-03 08:38:12 +00:00
}
```
2025-10-06 08:28:39 +00:00
#### 檢查黑名單
2025-10-03 08:38:12 +00:00
```go
2025-10-06 08:28:39 +00:00
isBlacklisted, err := tokenUseCase.IsTokenBlacklisted(ctx, jti)
if err != nil {
log.Printf("Failed to check blacklist: %v", err)
}
if isBlacklisted {
log.Println("Token is blacklisted")
}
```
2025-10-03 08:38:12 +00:00
2025-10-06 08:28:39 +00:00
## 🧪 測試
2025-10-03 08:38:12 +00:00
2025-10-06 08:28:39 +00:00
### 運行測試
2025-10-03 08:38:12 +00:00
2025-10-06 08:28:39 +00:00
```bash
# 運行所有測試
go test ./pkg/permission/...
2025-10-03 08:38:12 +00:00
2025-10-06 08:28:39 +00:00
# 運行特定模組測試
go test ./pkg/permission/usecase/
go test ./pkg/permission/repository/
2025-10-03 08:38:12 +00:00
2025-10-06 08:28:39 +00:00
# 運行測試並顯示覆蓋率
go test -cover ./pkg/permission/...
2025-10-03 08:38:12 +00:00
2025-10-06 08:28:39 +00:00
# 生成覆蓋率報告
go test -coverprofile=coverage.out ./pkg/permission/...
go tool cover -html=coverage.out
2025-10-03 08:38:12 +00:00
```
2025-10-06 08:28:39 +00:00
### 測試結構
2025-10-03 08:38:12 +00:00
2025-10-06 08:28:39 +00:00
- **UseCase Tests**: 業務邏輯測試,使用 Mock Repository
- **Repository Tests**: 資料存取測試,使用 MiniRedis
- **JWT Tests**: 權杖生成和解析測試
- **Integration Tests**: 整合測試
## 📊 API 參考
### TokenUseCase 介面
2025-10-03 08:38:12 +00:00
```go
2025-10-06 08:28:39 +00:00
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)
2025-10-03 08:38:12 +00:00
2025-10-06 08:28:39 +00:00
// 一次性 Token
NewOneTimeToken(ctx context.Context, req entity.CreateOneTimeTokenReq) (entity.CreateOneTimeTokenResp, error)
CancelOneTimeToken(ctx context.Context, req entity.CancelOneTimeTokenReq) error
2025-10-03 08:38:12 +00:00
2025-10-06 08:28:39 +00:00
// 黑名單操作
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)
2025-10-03 08:38:12 +00:00
}
```
2025-10-06 08:28:39 +00:00
### 主要實體
2025-10-03 08:38:12 +00:00
2025-10-06 08:28:39 +00:00
#### 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 過期時間(秒)
2025-10-03 08:38:12 +00:00
}
```
2025-10-06 08:28:39 +00:00
#### 黑名單實體
```go
type BlacklistEntry struct {
JTI string // JWT ID
UID string // 用戶 ID
TokenID string // Token ID
Reason string // 加入黑名單原因
ExpiresAt int64 // 原始權杖過期時間
CreatedAt int64 // 加入黑名單時間
}
2025-10-03 08:38:12 +00:00
```
2025-10-06 08:28:39 +00:00
## 🔧 配置參數
2025-10-03 08:38:12 +00:00
2025-10-06 08:28:39 +00:00
| 參數 | 類型 | 說明 | 預設值 |
|------|------|------|--------|
| `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 |
2025-10-03 08:38:12 +00:00
2025-10-06 08:28:39 +00:00
## 🚨 錯誤處理
2025-10-03 08:38:12 +00:00
2025-10-06 08:28:39 +00:00
模組定義了完整的錯誤類型:
2025-10-03 08:38:12 +00:00
```go
2025-10-06 08:28:39 +00:00
// 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")
)
2025-10-03 08:38:12 +00:00
```
2025-10-06 08:28:39 +00:00
## 🔒 安全考量
2025-10-03 08:38:12 +00:00
2025-10-06 08:28:39 +00:00
### 1. 密鑰管理
- 使用強密鑰(至少 256 位)
- Access Token 和 Refresh Token 使用不同密鑰
- 定期輪換密鑰
2025-10-03 08:38:12 +00:00
2025-10-06 08:28:39 +00:00
### 2. 權杖過期
- Access Token 使用較短過期時間15分鐘
- Refresh Token 使用較長過期時間7天
- 支援自定義過期時間
2025-10-03 08:38:12 +00:00
2025-10-06 08:28:39 +00:00
### 3. 黑名單機制
- 即時撤銷可疑權杖
- 支援批量撤銷
- 自動清理過期條目
2025-10-03 08:38:12 +00:00
2025-10-06 08:28:39 +00:00
### 4. 限制機制
- 每用戶權杖數量限制
- 每設備權杖數量限制
- 防止權杖濫用
2025-10-03 08:38:12 +00:00
2025-10-06 08:28:39 +00:00
## 📈 效能優化
2025-10-03 08:38:12 +00:00
2025-10-06 08:28:39 +00:00
### 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
- 聯絡維護團隊
2025-10-03 08:38:12 +00:00
2025-10-06 08:28:39 +00:00
---
2025-10-03 08:38:12 +00:00
2025-10-06 08:28:39 +00:00
**注意**: 本模組是 PlayOne Backend 專案的一部分,請確保與整體架構保持一致。