# 认证机制

<cite>
**本文档引用文件**  
- [auth.go](file://internal/middleware/auth.go)
- [user_token.go](file://utility/token/user_token.go)
- [config.go](file://utility/config/config.go)
- [sys_user_login_v1_user_login.go](file://internal/controller/sys_user_login/sys_user_login_v1_user_login.go)
- [v_1_sys_user.go](file://internal/model/entity/v_1_sys_user.go)
</cite>

## 目录
1. [简介](#简介)
2. [认证流程概述](#认证流程概述)
3. [登录接口实现](#登录接口实现)
4. [JWT令牌生成与解析](#jwt令牌生成与解析)
5. [中间件认证机制](#中间件认证机制)
6. [会话管理与令牌刷新](#会话管理与令牌刷新)
7. [安全配置选项](#安全配置选项)
8. [开发者集成指南](#开发者集成指南)
9. [常见问题解决方案](#常见问题解决方案)

## 简介
kami_backend系统采用基于JWT（JSON Web Token）的认证机制，结合Redis缓存实现安全的用户身份验证。系统支持多种认证方式，包括标准登录认证和IFrame嵌入式认证，通过中间件统一拦截和验证请求。认证流程涵盖用户凭证处理、令牌生成、验证、刷新和过期处理等关键环节，确保系统的安全性和可用性。

## 认证流程概述
kami_backend的认证流程分为两个主要部分：用户登录时的令牌生成和后续请求的令牌验证。系统采用双层认证策略，通过`LoginOrIframeAuth`中间件根据请求头中的`tokenFrom`字段决定使用哪种认证方式。对于公开接口，系统维护了一个白名单，允许无需认证的访问。

```mermaid
sequenceDiagram
participant 用户
participant 登录接口
participant 令牌生成器
participant Redis
participant 认证中间件
participant 业务接口
用户->>登录接口 : 提交用户名、密码、验证码
登录接口->>认证中间件 : 验证验证码
认证中间件-->>登录接口 : 验证结果
登录接口->>数据库 : 查询用户信息
数据库-->>登录接口 : 用户数据
登录接口->>令牌生成器 : 生成JWT令牌
令牌生成器->>Redis : 存储令牌
Redis-->>令牌生成器 : 存储确认
令牌生成器-->>登录接口 : 返回令牌
登录接口-->>用户 : 返回包含令牌的响应
用户->>业务接口 : 带令牌的请求
认证中间件->>Redis : 验证令牌有效性
Redis-->>认证中间件 : 令牌状态
认证中间件->>令牌解析器 : 解析JWT令牌
令牌解析器-->>认证中间件 : 用户信息
认证中间件->>令牌刷新器 : 检查是否需要刷新
令牌刷新器-->>认证中间件 : 新令牌如需
认证中间件-->>业务接口 : 继续处理请求
```

**图示来源**
- [auth.go](file://internal/middleware/auth.go)
- [user_token.go](file://utility/token/user_token.go)
- [sys_user_login_v1_user_login.go](file://internal/controller/sys_user_login/sys_user_login_v1_user_login.go)

## 登录接口实现
登录接口位于`internal/controller/sys_user_login/sys_user_login_v1_user_login.go`，负责处理用户登录请求。接口实现包含多层安全验证，确保认证过程的安全性。

### 认证步骤
1. **验证码验证**：首先验证用户提交的验证码是否正确
2. **用户名密码验证**：查询数据库验证用户名和密码
3. **双因素认证**：如果用户启用了TOTP，验证提供的验证码
4. **用户状态检查**：确认用户账户处于正常状态
5. **令牌生成**：通过`GenerateUserToken`函数生成JWT令牌

### 安全特性
- 验证码机制防止暴力破解
- 密码使用盐值加密存储
- 支持TOTP双因素认证
- 用户状态检查防止禁用账户登录

**本节来源**
- [sys_user_login_v1_user_login.go](file://internal/controller/sys_user_login/sys_user_login_v1_user_login.go)
- [v_1_sys_user.go](file://internal/model/entity/v_1_sys_user.go)

## JWT令牌生成与解析
系统使用`github.com/golang-jwt/jwt/v5`库实现JWT令牌的生成和解析，确保认证令牌的安全性和标准化。

### 令牌结构
```go
type UserToken struct {
    UserID   string
    Username string
    jwt.RegisteredClaims
}
```

### 令牌生成
`GenerateUserToken`函数负责生成JWT令牌，主要步骤包括：
1. 从配置中获取令牌选项（超时时间、加密密钥等）
2. 创建包含用户信息和声明的`UserToken`结构体
3. 使用HS256算法和配置的加密密钥签名生成令牌
4. 将令牌存储到Redis缓存中（如果配置了Redis模式）

```go
func GenerateUserToken(ctx context.Context, userName, userID string) (string, error) {
    tokenCfg, err := config.NewConfig(ctx).GetTokenOptions()
    if err != nil {
        return "", err
    }
    t := UserToken{
        UserID:   userID,
        Username: userName,
        RegisteredClaims: jwt.RegisteredClaims{
            Issuer:    "Auth Server",
            Subject:   userName,
            Audience:  nil,
            ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(tokenCfg.Timeout) * time.Second)),
            IssuedAt:  jwt.NewNumericDate(time.Now()),
            ID:        utils.GenerateRandomUUID(),
        },
    }
    token, err := t.genToken(ctx)
    if tokenCfg.CacheModel == CacheModelRedis {
        _ = cache.NewCache().Set(ctx, fmt.Sprintf("Token:%s:%s:%s", tokenCfg.CacheKey, t.UserID, t.ID), token, time.Duration(tokenCfg.Timeout*int64(time.Second)))
    }
    return token, err
}
```

### 令牌解析
`ParseUserToken`函数负责解析和验证JWT令牌：
1. 从请求中提取令牌（支持Bearer格式）
2. 使用配置的加密密钥验证令牌签名
3. 检查令牌是否过期
4. 返回解析后的用户信息

**本节来源**
- [user_token.go](file://utility/token/user_token.go)
- [config.go](file://utility/config/config.go)

## 中间件认证机制
认证中间件位于`internal/middleware/auth.go`，是整个系统认证的核心组件，负责拦截和验证所有请求的认证信息。

### 认证类型
系统支持三种认证方式：
- **白名单认证**：对特定路径的请求直接放行
- **登录认证**：基于JWT令牌的标准认证
- **IFrame认证**：基于AES加密的嵌入式认证

### 认证流程
```mermaid
flowchart TD
A[请求到达] --> B{路径在白名单?}
B --> |是| C[放行请求]
B --> |否| D{tokenFrom=login?}
D --> |是| E[执行登录认证]
D --> |否| F{tokenFrom=iframe?}
F --> |是| G[执行IFrame认证]
F --> |否| H[返回错误:token来源不明]
E --> I{认证成功?}
I --> |是| J[放行请求]
I --> |否| K[返回认证错误]
G --> L{认证成功?}
L --> |是| M[放行请求]
L --> |否| N[返回认证错误]
```

### 白名单路径
系统为特定接口配置了白名单，无需认证即可访问：
- `/api/merchant/order/query`
- `/api/restriction/block/order`
- `/api/cardInfo/appleCard/submit`
- 其他公开接口...

**本节来源**
- [auth.go](file://internal/middleware/auth.go)
- [user_token.go](file://utility/token/user_token.go)

## 会话管理与令牌刷新
系统实现了完善的会话管理机制，通过Redis存储令牌状态，并支持令牌自动刷新功能。

### 会话存储
- **存储模式**：支持内存和Redis两种模式，生产环境推荐使用Redis
- **存储键格式**：`Token:{cacheKey}:{userID}:{uuid}`
- **过期时间**：与令牌超时时间一致，自动同步过期

### 令牌刷新机制
系统在每次请求验证令牌时检查是否需要刷新：
1. 计算令牌已使用时间
2. 如果使用时间超过`MaxRefresh`配置但未过期，则生成新令牌
3. 将新令牌通过`refresh-token`响应头返回给客户端

```go
func RefreshUserToken(ctx context.Context, userToken UserToken) (newToken string, err error) {
    tokenCfg, err := config.NewConfig(ctx).GetTokenOptions()
    if err != nil {
        return "", err
    }
    issuerDate, err := userToken.GetIssuedAt()
    if err != nil {
        return "", errors.New("invalid token")
    }
    if tokenCfg.MaxRefresh == 0 {
        return "", nil
    }
    if time.Since(issuerDate.Time) > time.Duration(tokenCfg.MaxRefresh) &&
        time.Since(issuerDate.Time) < time.Duration(tokenCfg.Timeout) {
        newToken, err = GenerateUserToken(ctx, userToken.Username, userToken.UserID)
        return newToken, err
    }
    return
}
```

### 令牌注销
系统提供`DeleteTokenFromRedis`函数用于主动注销令牌，通常在用户登出时调用。

**本节来源**
- [user_token.go](file://utility/token/user_token.go)
- [config.go](file://utility/config/config.go)

## 安全配置选项
系统通过配置文件灵活管理认证相关的安全参数，所有配置位于`manifest/config/config.yaml`。

### 配置项说明
| 配置项 | 默认值 | 说明 |
|-------|-------|------|
| `token.timeout` | 3600 | 令牌有效期（秒） |
| `token.maxRefresh` | 1800 | 令牌最大刷新时间（秒） |
| `token.cacheModel` | redis | 缓存模式（memory/redis） |
| `token.cacheKey` | user_token | Redis缓存键前缀 |
| `token.encryptKey` | 随机生成 | JWT签名加密密钥 |
| `secret.frontend.key` | 随机生成 | 前端加密密钥 |
| `secret.frontend.iv` | 随机生成 | 前端加密IV向量 |

### 配置加载
配置通过`utility/config/config.go`中的`GetTokenOptions`函数加载：
```go
func (c *Config) GetTokenOptions() (opts TokenOptions, err error) {
    result, err := c.getDataByKey("token")
    if err != nil {
        return
    }
    if err2 := result.Struct(&opts); err2 != nil {
        err = err2
        return
    }
    return
}
```

### 安全建议
- 定期轮换`encryptKey`和`secret`配置
- 生产环境必须使用Redis模式而非内存模式
- 根据业务需求合理设置`timeout`和`maxRefresh`值
- 监控异常登录行为并及时响应

**本节来源**
- [config.go](file://utility/config/config.go)
- [user_token.go](file://utility/token/user_token.go)

## 开发者集成指南
本指南为开发者提供认证机制的集成方法和最佳实践。

### 前端集成
1. **登录流程**：
   - 调用`/api/sysUser/login`接口提交认证信息
   - 保存返回的令牌用于后续请求
   - 监听`refresh-token`响应头更新本地令牌

2. **请求认证**：
   - 在请求头中添加`Authorization: Bearer {token}`
   - 或在请求头中添加`tokenFrom: login`

3. **错误处理**：
   - `401 Unauthorized`：令牌无效或过期，需要重新登录
   - `403 Forbidden`：权限不足
   - `429 Too Many Requests`：请求过于频繁

### 后端集成
1. **保护接口**：在路由中使用`LoginOrIframeAuth`中间件
2. **获取用户信息**：通过上下文获取已认证的用户信息
3. **自定义认证**：可根据需要扩展认证逻辑

### 测试建议
- 使用`user_token_test.go`中的测试用例验证令牌功能
- 模拟各种异常情况（过期令牌、无效签名等）
- 测试白名单路径的访问权限

**本节来源**
- [auth.go](file://internal/middleware/auth.go)
- [user_token.go](file://utility/token/user_token.go)
- [sys_user_login_v1_user_login.go](file://internal/controller/sys_user_login/sys_user_login_v1_user_login.go)

## 常见问题解决方案
### 问题1：登录成功但后续请求返回401错误
**可能原因**：
- 令牌未正确添加到请求头
- 令牌格式错误（缺少Bearer前缀）
- Redis连接问题导致令牌验证失败

**解决方案**：
1. 检查请求头中`Authorization`字段格式是否正确
2. 确认Redis服务正常运行
3. 检查`token.cacheKey`配置是否一致

### 问题2：令牌频繁过期
**可能原因**：
- `timeout`配置值过小
- 系统时间不同步
- 令牌刷新机制未正常工作

**解决方案**：
1. 适当增加`token.timeout`配置值
2. 确保服务器时间同步
3. 检查`maxRefresh`配置是否合理

### 问题3：IFrame认证失败
**可能原因**：
- `tokenFrom`头未设置为`iframe`
- 前端加密密钥或IV配置错误
- 令牌超过30分钟有效期

**解决方案**：
1. 确认请求头中`tokenFrom: iframe`
2. 检查`secret.frontend.key`和`secret.frontend.iv`配置
3. 确保前端正确生成加密令牌

### 问题4：Redis存储的令牌无法读取
**可能原因**：
- 缓存键前缀不匹配
- Redis数据库选择错误
- 序列化格式不一致

**解决方案**：
1. 检查`token.cacheKey`配置
2. 确认Redis连接配置
3. 验证序列化和反序列化逻辑

**本节来源**
- [auth.go](file://internal/middleware/auth.go)
- [user_token.go](file://utility/token/user_token.go)
- [config.go](file://utility/config/config.go)