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