first commit

This commit is contained in:
2026-02-13 15:57:29 +08:00
commit aacda0b66a
53 changed files with 10029 additions and 0 deletions

608
docs/api/authentication.md Normal file
View File

@@ -0,0 +1,608 @@
# 认证机制详解
本文档详细说明 Web Rust Template 的 JWT 认证机制、安全特性和最佳实践。
## 目录
- [认证架构概述](#认证架构概述)
- [双 Token 机制](#双-token-机制)
- [认证流程](#认证流程)
- [Token 管理](#token-管理)
- [安全特性](#安全特性)
- [最佳实践](#最佳实践)
---
## 认证架构概述
本系统采用 **JWT (JSON Web Token)** 进行用户认证,使用 **双 Token 机制**
1. **Access Token**:短期有效,用于 API 请求认证
2. **Refresh Token**:长期有效,用于获取新的 Access Token
### 架构特点
-**无状态认证**:服务器不存储会话信息,易于扩展
-**安全性**Token 泄露影响可控,自动过期
-**用户体验**Refresh Token 可减少用户登录次数
-**可撤销性**:通过 Redis 存储 Refresh Token支持主动撤销
---
## 双 Token 机制
### Access Token
**用途**:访问需要认证的 API 接口
**特点**
- 有效期15 分钟(可配置)
- 包含用户 ID 和 Token 类型信息
- 不存储在服务器端(无状态)
- 每次请求都通过 HTTP Header 传递
**格式**
```http
Authorization: Bearer <access_token>
```
### Refresh Token
**用途**:获取新的 Access Token
**特点**
- 有效期7 天(可配置)
- 存储在 Redis 中
- 支持撤销和主动登出
- 每次刷新会生成新的 Refresh Token
**存储位置**
- 前端localStorage 或 sessionStorage
- 后端RedisKey`auth:refresh_token:<user_id>`
---
## 认证流程
### 1. 用户注册流程
```mermaid
sequenceDiagram
participant User as 用户
participant Frontend as 前端应用
participant API as API 服务器
participant DB as 数据库
participant Redis as Redis
User->>Frontend: 输入邮箱和密码
Frontend->>API: POST /auth/register
API->>API: 验证邮箱格式
API->>API: 生成用户 ID
API->>API: 哈希密码Argon2
API->>DB: 创建用户记录
DB-->>API: 用户创建成功
API->>API: 生成 Access Token (15min)
API->>API: 生成 Refresh Token (7days)
API->>Redis: 存储 Refresh Token
Redis-->>API: 存储成功
API-->>Frontend: 返回 Access Token + Refresh Token
Frontend->>Frontend: 存储 Token 到 localStorage
Frontend-->>User: 注册成功,自动登录
```
**关键点**
- 密码使用 Argon2 算法哈希,不可逆
- Refresh Token 存储在 Redis支持撤销
- 注册成功后自动登录,返回 Token
### 2. 用户登录流程
```mermaid
sequenceDiagram
participant User as 用户
participant Frontend as 前端应用
participant API as API 服务器
participant DB as 数据库
participant Redis as Redis
User->>Frontend: 输入邮箱和密码
Frontend->>API: POST /auth/login
API->>DB: 查询用户记录
DB-->>API: 返回用户信息
API->>API: 验证密码Argon2
API->>API: 生成 Access Token (15min)
API->>API: 生成 Refresh Token (7days)
API->>Redis: 存储/更新 Refresh Token
Redis-->>API: 存储成功
API-->>Frontend: 返回 Access Token + Refresh Token
Frontend->>Frontend: 存储 Token 到 localStorage
Frontend-->>User: 登录成功
```
**安全特性**
- 登录失败不返回具体错误信息(防止账号枚举)
- 密码错误会记录日志用于风控
- Refresh Token 每次登录都会更新
### 3. 访问受保护接口流程
```mermaid
sequenceDiagram
participant Frontend as 前端应用
participant API as API 服务器
participant Redis as Redis
Frontend->>API: GET /protected<br/>Authorization: Bearer <access_token>
API->>API: 验证 JWT 签名
API->>API: 检查 Token 类型
API->>API: 检查 Token 过期时间
alt Token 有效
API-->>Frontend: 200 OK 返回数据
else Token 无效或过期
API-->>Frontend: 401 Unauthorized
Frontend->>API: POST /auth/refresh
API->>Redis: 获取 Refresh Token
Redis-->>API: 返回 Refresh Token
API->>API: 验证 Refresh Token
API->>API: 生成新的 Token 对
API->>Redis: 更新 Refresh Token
API-->>Frontend: 返回新的 Token
Frontend->>API: 重试原请求
API-->>Frontend: 200 OK 返回数据
end
```
**关键点**
- 所有受保护接口都需要在 Header 中携带 Access Token
- Token 过期时前端自动刷新并重试请求
- 刷新成功后旧 Refresh Token 立即失效
### 4. Token 刷新流程
```mermaid
sequenceDiagram
participant Frontend as 前端应用
participant API as API 服务器
participant Redis as Redis
Frontend->>API: POST /auth/refresh<br/>{"refresh_token": "..."}
API->>API: 验证 Refresh Token 签名
API->>API: 检查 Token 类型(必须是 Refresh Token
API->>API: 检查 Token 过期时间
API->>Redis: 检查 Refresh Token 是否存在
alt Token 有效
API->>API: 生成新的 Access Token (15min)
API->>API: 生成新的 Refresh Token (7days)
API->>Redis: 删除旧的 Refresh Token
API->>Redis: 存储新的 Refresh Token
API-->>Frontend: 返回新的 Token 对
else Token 无效或过期
API-->>Frontend: 401 Unauthorized
Frontend->>Frontend: 清除 Token
Frontend->>Frontend: 跳转到登录页
end
```
**Token 轮换**
- 每次刷新都会生成新的 Refresh Token
- 旧的 Refresh Token 立即失效
- 防止 Token 重放攻击
### 5. 用户登出流程
```mermaid
sequenceDiagram
participant User as 用户
participant Frontend as 前端应用
participant API as API 服务器
participant Redis as Redis
User->>Frontend: 点击登出按钮
Frontend->>API: POST /auth/delete-refresh-token<br/>Authorization: Bearer <access_token>
API->>API: 验证 Access Token
API->>API: 从 Token 中提取 user_id
API->>Redis: 删除 Refresh Token
Redis-->>API: 删除成功
API-->>Frontend: 200 OK
Frontend->>Frontend: 清除本地 Token
Frontend->>Frontend: 跳转到登录页
Frontend-->>User: 登出成功
```
---
## Token 管理
### Token 生成
```rust
// src/utils/jwt.rs
// 生成 Access Token
pub fn generate_access_token(
user_id: &str,
expiration_minutes: u64,
jwt_secret: &str,
) -> Result<String> {
let expiration = Utc::now()
.checked_add_signed(Duration::minutes(expiration_minutes as i64))
.expect("invalid expiration timestamp")
.timestamp() as usize;
let claims = Claims {
sub: user_id.to_string(),
exp: expiration,
token_type: TokenType::Access,
};
let token = encode(
&Header::default(),
&claims,
&EncodingKey::from_secret(jwt_secret.as_ref()),
)?;
Ok(token)
}
// 生成 Refresh Token
pub fn generate_refresh_token(
user_id: &str,
expiration_days: i64,
jwt_secret: &str,
) -> Result<String> {
let expiration = Utc::now()
.checked_add_signed(Duration::days(expiration_days))
.expect("invalid expiration timestamp")
.timestamp() as usize;
let claims = Claims {
sub: user_id.to_string(),
exp: expiration,
token_type: TokenType::Refresh,
};
let token = encode(
&Header::default(),
&claims,
&EncodingKey::from_secret(jwt_secret.as_ref()),
)?;
Ok(token)
}
```
### Token 验证
```rust
// src/infra/middleware/auth.rs
pub async fn auth_middleware(
State(state): State<AppState>,
mut request: Request,
next: Next,
) -> Result<Response, ErrorResponse> {
// 1. 提取 Authorization header
let auth_header = request
.headers()
.get("Authorization")
.and_then(|h| h.to_str().ok())
.ok_or_else(|| ErrorResponse::new("缺少 Authorization header".to_string()))?;
// 2. 验证 Bearer 格式
if !auth_header.starts_with("Bearer ") {
return Err(ErrorResponse::new("Authorization header 格式错误".to_string()));
}
let token = &auth_header[7..]; // 跳过 "Bearer "
// 3. 验证 JWT 签名和过期时间
let claims = decode_token(token, &state.config.auth.jwt_secret)?;
// 4. 检查 Token 类型(必须是 Access Token
if claims.token_type != TokenType::Access {
return Err(ErrorResponse::new("Token 类型错误".to_string()));
}
// 5. 将 user_id 添加到请求扩展中
let user_id = claims.sub;
request.extensions_mut().insert(user_id);
// 6. 继续处理请求
Ok(next.run(request).await)
}
```
### Refresh Token 存储
```rust
// src/services/auth_service.rs
async fn save_refresh_token(&self, user_id: &str, refresh_token: &str, expiration_days: i64) -> Result<()> {
let key = RedisKey::new(BusinessType::Auth)
.add_identifier("refresh_token")
.add_identifier(user_id);
let expiration_seconds = expiration_days * 24 * 3600;
self.redis_client
.set_ex(&key.build(), refresh_token, expiration_seconds as u64)
.await
.map_err(|e| anyhow::anyhow!("Redis 保存失败: {}", e))?;
Ok(())
}
```
**Redis Key 设计**
```
auth:refresh_token:<user_id>
```
**过期时间**7 天(与 Refresh Token 有效期一致)
---
## 安全特性
### 1. 密码安全
**Argon2 哈希**
- 使用 Argon2 算法(内存 hard抗 GPU/ASIC 破解)
- 自动生成随机盐值
- 哈希结果不可逆
```rust
// src/services/auth_service.rs
pub fn hash_password(&self, password: &str) -> Result<String> {
let salt = SaltString::generate(&mut OsRng);
let argon2 = Argon2::default();
let password_hash = argon2
.hash_password(password.as_bytes(), &salt)
.map_err(|e| anyhow::anyhow!("密码哈希失败: {}", e))?
.to_string();
Ok(password_hash)
}
```
### 2. JWT 安全
**签名算法**HS256 (HMAC-SHA256)
**Claims 结构**
```rust
pub struct Claims {
pub sub: String, // 用户 ID
pub exp: usize, // 过期时间Unix 时间戳)
pub token_type: TokenType, // Token 类型Access/Refresh
}
```
**安全措施**
- 使用强密钥(至少 32 位随机字符串)
- Token 包含过期时间
- Token 类型区分(防止混用)
- 签名验证防止篡改
### 3. Refresh Token 安全
**存储安全**
- 存储在 Redis 中,支持快速撤销
- 每次刷新生成新 Token旧 Token 失效
- 支持主动登出,删除 Refresh Token
**使用限制**
- Refresh Token 只能使用一次
- 过期后无法续期
- 需要重新登录
### 4. 防护措施
**防重放攻击**
- Refresh Token 单次使用
- 刷新后立即失效
**防 Token 泄露**
- Access Token 短期有效15 分钟)
- 只通过 HTTPS 传输
- 不在 URL 中传递
**防暴力破解**
- 限制登录频率(可选实现)
- 记录失败尝试(日志)
- 密码哈希使用 Argon2
---
## 最佳实践
### 前端集成
#### 1. Token 存储
**推荐方案**
```typescript
// 存储 Token
localStorage.setItem('access_token', access_token);
localStorage.setItem('refresh_token', refresh_token);
// 读取 Token
const accessToken = localStorage.getItem('access_token');
const refreshToken = localStorage.getItem('refresh_token');
// 清除 Token
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
```
#### 2. 请求拦截器
```typescript
// 添加 Token 到请求头
api.interceptors.request.use((config) => {
const accessToken = localStorage.getItem('access_token');
if (accessToken) {
config.headers.Authorization = `Bearer ${accessToken}`;
}
return config;
});
```
#### 3. 响应拦截器(自动刷新 Token
```typescript
// 处理 401 错误并自动刷新
api.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
try {
const refreshToken = localStorage.getItem('refresh_token');
const response = await axios.post('/auth/refresh', {
refresh_token: refreshToken,
});
const { access_token, refresh_token } = response.data.data;
localStorage.setItem('access_token', access_token);
localStorage.setItem('refresh_token', refresh_token);
// 重试原请求
originalRequest.headers.Authorization = `Bearer ${access_token}`;
return axios(originalRequest);
} catch (refreshError) {
// 刷新失败,跳转登录页
localStorage.clear();
window.location.href = '/login';
return Promise.reject(refreshError);
}
}
return Promise.reject(error);
}
);
```
### 后端开发
#### 1. 密码强度要求
```rust
// 验证密码强度
fn validate_password(password: &str) -> Result<()> {
if password.len() < 8 {
return Err(anyhow!("密码长度至少 8 位"));
}
if password.len() > 100 {
return Err(anyhow!("密码长度最多 100 位"));
}
// 可添加更多规则(如必须包含大小写、数字等)
Ok(())
}
```
#### 2. JWT 密钥管理
**开发环境**
使用 `config/` 目录下的配置文件:
```bash
# 方式1使用默认配置推荐
# JWT 密钥已在 config/default.toml 中配置
# 方式2创建本地配置文件
cp config/default.toml config/local.toml
# 编辑 config/local.toml修改 jwt_secret
nano config/local.toml
# 运行
cargo run -- -c config/local.toml
```
**生产环境**
```bash
# 使用强随机密钥
AUTH_JWT_SECRET=$(openssl rand -base64 32)
```
#### 3. Token 过期时间配置
```bash
# Access Token15 分钟(推荐)
AUTH_ACCESS_TOKEN_EXPIRATION_MINUTES=15
# Refresh Token7 天(推荐)
AUTH_REFRESH_TOKEN_EXPIRATION_DAYS=7
```
**建议**
- Access Token5-30 分钟(权衡安全性和用户体验)
- Refresh Token7-30 天(根据应用安全要求)
### 生产部署
#### 1. HTTPS 强制
```nginx
server {
listen 80;
server_name api.yourdomain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
server_name api.yourdomain.com;
# SSL 配置...
}
```
#### 2. CORS 配置
开发环境可以允许所有来源:
```rust
CorsLayer::new()
.allow_origin(Any)
.allow_methods(Any)
.allow_headers(Any)
```
生产环境应该限制允许的来源:
```rust
CorsLayer::new()
.allow_origin("https://yourdomain.com".parse::<HeaderValue>().unwrap())
.allow_methods([Method::GET, Method::POST, Method::PUT, Method::DELETE])
.allow_headers([HeaderName::from_static("content-type"), HeaderName::from_static("authorization")])
```
#### 3. 速率限制
防止暴力破解和 DDoS 攻击(需要额外实现):
```rust
// 使用 governor 库实现速率限制
use governor::{Quota, RateLimiter};
let limiter = RateLimiter::direct(Quota::per_second(5));
// 每秒最多 5 个请求
```
---
## 相关文档
- [公开接口文档](endpoints/public.md) - 注册、登录、刷新 Token 接口
- [受保护接口文档](endpoints/protected.md) - 需要认证的接口
- [前端集成示例](examples/frontend-integration.md) - 完整的前端集成代码
- [环境变量配置](../deployment/environment-variables.md) - 认证相关配置说明
---
**提示**:生产环境部署前务必修改 JWT 密钥为强随机字符串!