chore(git): 更新.gitignore以忽略新的本地文件
Some checks failed
SonarQube Analysis / sonarqube (push) Has been cancelled
Some checks failed
SonarQube Analysis / sonarqube (push) Has been cancelled
This commit is contained in:
310
.qoder/repowiki/zh/content/认证与授权/JWT认证.md
Normal file
310
.qoder/repowiki/zh/content/认证与授权/JWT认证.md
Normal file
@@ -0,0 +1,310 @@
|
||||
# JWT认证
|
||||
|
||||
<cite>
|
||||
**本文档引用的文件**
|
||||
- [jwt.go](file://pkg/auth/jwt.go)
|
||||
- [manager.go](file://pkg/auth/manager.go)
|
||||
- [auth.go](file://internal/middleware/auth.go)
|
||||
- [auth_handler.go](file://internal/handler/auth_handler.go)
|
||||
- [user_service.go](file://internal/service/user_service.go)
|
||||
- [config.go](file://pkg/config/config.go)
|
||||
</cite>
|
||||
|
||||
## 目录
|
||||
1. [简介](#简介)
|
||||
2. [JWT服务结构设计](#jwt服务结构设计)
|
||||
3. [令牌生成与验证机制](#令牌生成与验证机制)
|
||||
4. [认证中间件实现](#认证中间件实现)
|
||||
5. [JWT工作流程](#jwt工作流程)
|
||||
6. [密钥管理与安全策略](#密钥管理与安全策略)
|
||||
7. [单例模式应用](#单例模式应用)
|
||||
8. [最佳实践](#最佳实践)
|
||||
|
||||
## 简介
|
||||
CarrotSkin项目采用基于JWT(JSON Web Token)的用户认证机制,为系统提供安全、无状态的用户身份验证。该机制通过在客户端和服务器之间传递加密的令牌来验证用户身份,避免了传统会话存储的服务器状态管理开销。本文档详细阐述了JWT服务的实现原理、核心组件设计以及在Gin框架中的集成方式,为开发者提供全面的技术参考。
|
||||
|
||||
## JWT服务结构设计
|
||||
|
||||
JWT服务的核心是`JWTService`结构体,它封装了令牌生成和验证所需的所有配置和方法。该服务通过`Claims`结构体定义了令牌中包含的用户声明信息。
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class JWTService {
|
||||
-secretKey string
|
||||
-expireHours int
|
||||
+GenerateToken(userID int64, username string, role string) (string, error)
|
||||
+ValidateToken(tokenString string) (*Claims, error)
|
||||
}
|
||||
class Claims {
|
||||
+UserID int64
|
||||
+Username string
|
||||
+Role string
|
||||
+RegisteredClaims jwt.RegisteredClaims
|
||||
}
|
||||
JWTService --> Claims : "包含"
|
||||
```
|
||||
|
||||
**Diagram sources**
|
||||
- [jwt.go](file://pkg/auth/jwt.go#L11-L14)
|
||||
- [jwt.go](file://pkg/auth/jwt.go#L25-L30)
|
||||
|
||||
**Section sources**
|
||||
- [jwt.go](file://pkg/auth/jwt.go#L11-L71)
|
||||
|
||||
## 令牌生成与验证机制
|
||||
|
||||
### 令牌生成(GenerateToken)
|
||||
`GenerateToken`方法负责创建JWT令牌,它接收用户ID、用户名和角色作为参数,构建包含这些信息的声明(Claims),并使用预设的密钥进行签名。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([开始生成令牌]) --> CreateClaims["创建声明对象<br/>包含user_id、username、role"]
|
||||
CreateClaims --> SetExpiry["设置过期时间<br/>(当前时间 + expireHours)"]
|
||||
SetExpiry --> SetIssued["设置签发时间<br/>(当前时间)"]
|
||||
SetIssued --> SetNotBefore["设置生效时间<br/>(当前时间)"]
|
||||
SetNotBefore --> SetIssuer["设置发行者<br/>(carrotskin)"]
|
||||
SetIssuer --> CreateToken["创建JWT令牌<br/>使用HS256算法"]
|
||||
CreateToken --> SignToken["使用secretKey签名"]
|
||||
SignToken --> ReturnToken["返回签名后的令牌字符串"]
|
||||
ReturnToken --> End([结束])
|
||||
```
|
||||
|
||||
**Diagram sources**
|
||||
- [jwt.go](file://pkg/auth/jwt.go#L33-L53)
|
||||
|
||||
### 令牌验证(ValidateToken)
|
||||
`ValidateToken`方法负责验证接收到的JWT令牌的有效性,包括签名验证、过期检查和声明解析。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([开始验证令牌]) --> ParseToken["解析令牌字符串"]
|
||||
ParseToken --> CheckSignature["验证签名<br/>使用secretKey"]
|
||||
CheckSignature --> IsValid{"签名有效?"}
|
||||
IsValid --> |否| ReturnError["返回错误<br/>无效的token"]
|
||||
IsValid --> |是| CheckClaims["检查声明对象"]
|
||||
CheckClaims --> IsClaimsValid{"声明类型正确<br/>且令牌有效?"}
|
||||
IsClaimsValid --> |否| ReturnError
|
||||
IsClaimsValid --> |是| ReturnClaims["返回解析出的Claims对象"]
|
||||
ReturnClaims --> End([结束])
|
||||
ReturnError --> End
|
||||
```
|
||||
|
||||
**Diagram sources**
|
||||
- [jwt.go](file://pkg/auth/jwt.go#L56-L70)
|
||||
|
||||
**Section sources**
|
||||
- [jwt.go](file://pkg/auth/jwt.go#L32-L70)
|
||||
|
||||
## 认证中间件实现
|
||||
|
||||
CarrotSkin项目提供了两种认证中间件:强制认证中间件(AuthMiddleware)和可选认证中间件(OptionalAuthMiddleware),它们在Gin框架中拦截HTTP请求并处理Authorization头。
|
||||
|
||||
### 强制认证中间件(AuthMiddleware)
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client as "客户端"
|
||||
participant Middleware as "AuthMiddleware"
|
||||
participant JWTService as "JWTService"
|
||||
Client->>Middleware : 发送请求<br/>Authorization : Bearer token
|
||||
Middleware->>Middleware : 获取Authorization头
|
||||
alt 头部为空
|
||||
Middleware-->>Client : 返回401<br/>缺少Authorization头
|
||||
return
|
||||
end
|
||||
Middleware->>Middleware : 解析Bearer格式
|
||||
alt 格式无效
|
||||
Middleware-->>Client : 返回401<br/>无效的Authorization头格式
|
||||
return
|
||||
end
|
||||
Middleware->>JWTService : 调用ValidateToken(token)
|
||||
alt 验证失败
|
||||
JWTService-->>Middleware : 返回错误
|
||||
Middleware-->>Client : 返回401<br/>无效的token
|
||||
return
|
||||
end
|
||||
JWTService-->>Middleware : 返回Claims对象
|
||||
Middleware->>Middleware : 将用户信息存入上下文<br/>(user_id, username, role)
|
||||
Middleware->>Client : 继续处理请求
|
||||
```
|
||||
|
||||
### 可选认证中间件(OptionalAuthMiddleware)
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client as "客户端"
|
||||
participant Middleware as "OptionalAuthMiddleware"
|
||||
participant JWTService as "JWTService"
|
||||
Client->>Middleware : 发送请求<br/>Authorization : Bearer token
|
||||
Middleware->>Middleware : 获取Authorization头
|
||||
alt 头部存在
|
||||
Middleware->>Middleware : 解析Bearer格式
|
||||
alt 格式正确
|
||||
Middleware->>JWTService : 调用ValidateToken(token)
|
||||
alt 验证成功
|
||||
JWTService-->>Middleware : 返回Claims对象
|
||||
Middleware->>Middleware : 将用户信息存入上下文
|
||||
end
|
||||
end
|
||||
end
|
||||
Middleware->>Client : 继续处理请求<br/>(无论认证成功与否)
|
||||
```
|
||||
|
||||
**Diagram sources**
|
||||
- [auth.go](file://internal/middleware/auth.go#L13-L55)
|
||||
- [auth.go](file://internal/middleware/auth.go#L58-L78)
|
||||
|
||||
**Section sources**
|
||||
- [auth.go](file://internal/middleware/auth.go#L1-L79)
|
||||
|
||||
## JWT工作流程
|
||||
|
||||
以下是用户从登录到访问受保护资源的完整JWT工作流程:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User as "用户"
|
||||
participant Frontend as "前端"
|
||||
participant Backend as "后端"
|
||||
User->>Frontend : 输入用户名/邮箱和密码
|
||||
Frontend->>Backend : POST /api/v1/auth/login
|
||||
Backend->>Backend : 调用LoginUser服务
|
||||
Backend->>Backend : 验证用户凭证
|
||||
alt 验证成功
|
||||
Backend->>Backend : 调用JWTService.GenerateToken
|
||||
Backend->>Backend : 生成JWT令牌
|
||||
Backend-->>Frontend : 返回200<br/>{token, userInfo}
|
||||
Frontend->>Frontend : 存储令牌
|
||||
User->>Frontend : 访问受保护页面
|
||||
Frontend->>Backend : GET /api/v1/protected<br/>Authorization : Bearer token
|
||||
Backend->>Backend : AuthMiddleware拦截请求
|
||||
Backend->>Backend : 解析Authorization头
|
||||
Backend->>Backend : 调用JWTService.ValidateToken
|
||||
Backend->>Backend : 验证令牌有效性
|
||||
Backend->>Backend : 提取用户信息存入上下文
|
||||
Backend->>Backend : 继续处理业务逻辑
|
||||
Backend-->>Frontend : 返回请求数据
|
||||
else 验证失败
|
||||
Backend-->>Frontend : 返回401<br/>登录失败
|
||||
end
|
||||
```
|
||||
|
||||
**Diagram sources**
|
||||
- [auth_handler.go](file://internal/handler/auth_handler.go#L97-L147)
|
||||
- [user_service.go](file://internal/service/user_service.go#L71-L122)
|
||||
- [auth.go](file://internal/middleware/auth.go#L13-L55)
|
||||
|
||||
**Section sources**
|
||||
- [auth_handler.go](file://internal/handler/auth_handler.go#L97-L147)
|
||||
- [user_service.go](file://internal/service/user_service.go#L71-L122)
|
||||
|
||||
## 密钥管理与安全策略
|
||||
|
||||
### 配置管理
|
||||
JWT服务的配置通过`config.go`文件中的`JWTConfig`结构体进行管理,支持环境变量注入,确保密钥的安全性。
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class JWTConfig {
|
||||
+Secret string
|
||||
+ExpireHours int
|
||||
}
|
||||
class Config {
|
||||
+Server ServerConfig
|
||||
+Database DatabaseConfig
|
||||
+Redis RedisConfig
|
||||
+JWT JWTConfig
|
||||
+Casbin CasbinConfig
|
||||
+Log LogConfig
|
||||
+Upload UploadConfig
|
||||
+Email EmailConfig
|
||||
}
|
||||
Config --> JWTConfig : "包含"
|
||||
```
|
||||
|
||||
**Diagram sources**
|
||||
- [config.go](file://pkg/config/config.go#L68-L71)
|
||||
- [config.go](file://pkg/config/config.go#L14-L24)
|
||||
|
||||
### 安全声明
|
||||
JWT令牌中包含的声明(Claims)具有重要的安全意义:
|
||||
|
||||
- **user_id**: 用户唯一标识,用于在系统中识别用户身份
|
||||
- **username**: 用户名,用于显示和审计
|
||||
- **role**: 用户角色,用于基于角色的访问控制(RBAC)
|
||||
- **ExpiresAt**: 过期时间,防止令牌长期有效
|
||||
- **IssuedAt**: 签发时间,用于审计和令牌生命周期管理
|
||||
- **NotBefore**: 生效时间,可用于延迟生效的令牌
|
||||
- **Issuer**: 发行者,确保令牌来自可信源
|
||||
|
||||
**Section sources**
|
||||
- [jwt.go](file://pkg/auth/jwt.go#L25-L30)
|
||||
- [config.go](file://pkg/config/config.go#L68-L71)
|
||||
|
||||
## 单例模式应用
|
||||
|
||||
`MustGetJWTService`函数实现了线程安全的单例模式,确保在整个应用程序生命周期中只存在一个JWT服务实例。
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Init as "初始化"
|
||||
participant Get as "GetJWTService"
|
||||
participant MustGet as "MustGetJWTService"
|
||||
participant Once as "sync.Once"
|
||||
Init->>Once : 调用Init(cfg)
|
||||
Once->>Once : 检查是否已执行
|
||||
alt 首次调用
|
||||
Once->>Init : 执行初始化
|
||||
Init->>jwtServiceInstance : 创建JWTService实例
|
||||
Init-->>Once : 标记已执行
|
||||
else 非首次调用
|
||||
Once-->>Init : 跳过执行
|
||||
end
|
||||
Get->>Get : 检查jwtServiceInstance
|
||||
alt 实例存在
|
||||
Get-->>Get : 返回实例
|
||||
else 实例不存在
|
||||
Get-->>Get : 返回错误
|
||||
end
|
||||
MustGet->>Get : 调用GetJWTService
|
||||
alt 获取成功
|
||||
Get-->>MustGet : 返回实例
|
||||
MustGet-->>MustGet : 返回实例
|
||||
else 获取失败
|
||||
Get-->>MustGet : 返回错误
|
||||
MustGet->>MustGet : panic(错误)
|
||||
end
|
||||
```
|
||||
|
||||
**Diagram sources**
|
||||
- [manager.go](file://pkg/auth/manager.go#L9-L45)
|
||||
|
||||
**Section sources**
|
||||
- [manager.go](file://pkg/auth/manager.go#L9-L45)
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 密钥管理
|
||||
- JWT密钥(`secretKey`)应通过环境变量配置,避免硬编码在代码中
|
||||
- 使用足够长度的随机字符串作为密钥,提高安全性
|
||||
- 定期轮换密钥(虽然当前实现不支持,但架构上可以扩展)
|
||||
|
||||
### 过期策略
|
||||
- 当前默认过期时间为168小时(7天),可通过`JWT_EXPIRE_HOURS`环境变量调整
|
||||
- 对于敏感操作,建议使用短期令牌或结合刷新令牌机制
|
||||
- 过期时间不宜过长,以降低令牌泄露后的风险
|
||||
|
||||
### 防止重放攻击
|
||||
- 虽然JWT本身是无状态的,但可以通过以下方式增强安全性:
|
||||
- 使用短期令牌
|
||||
- 在Redis中维护已注销令牌的黑名单
|
||||
- 结合客户端指纹(如IP地址、User-Agent)进行额外验证
|
||||
- 当前实现中,`OptionalAuthMiddleware`提供了基础的灵活性,允许部分接口在认证失败时仍能访问
|
||||
|
||||
### 性能考虑
|
||||
- JWT验证是计算密集型操作,但在现代硬件上性能良好
|
||||
- 使用HS256算法在安全性和性能之间取得了良好平衡
|
||||
- 避免在令牌中存储过多信息,以减小令牌大小和传输开销
|
||||
|
||||
**Section sources**
|
||||
- [config.go](file://pkg/config/config.go#L164)
|
||||
- [manager.go](file://pkg/auth/manager.go#L18-L23)
|
||||
- [jwt.go](file://pkg/auth/jwt.go#L39-L43)
|
||||
Reference in New Issue
Block a user