> ## Documentation Index
> Fetch the complete documentation index at: https://docs.profy.cn/llms.txt
> Use this file to discover all available pages before exploring further.

# SMS、JWT 与 API Key 认证体系

> Profy 认证系统说明：终端用户使用 SMS 短信登录与 JWT 会话，程序接入使用 API Key，全部由 Redis 存储并支持单设备登录策略。

# 认证系统

Profy 采用 SMS 验证码 + JWT Token 的无密码认证方案，通过 Redis 实现会话管理和单设备策略。针对第三方集成，提供基于 API Key 的 OpenAPI 认证通道。

## 概述

```mermaid theme={null}
graph TB
    subgraph 用户认证
        SMS["SMS 验证码登录"]
        JWT["JWT Token 签发"]
        Session["Redis 会话存储"]
        Single["单设备策略"]
    end

    subgraph API 认证
        Key["API Key"]
        OpenAPI["OpenAPI 路由"]
    end

    SMS --> JWT --> Session --> Single
    Key --> OpenAPI

    subgraph 中间件
        AuthFilter["Auth Filter<br/>/api/*"]
        KeyFilter["API Key Filter<br/>/openapi/*"]
    end

    Session --> AuthFilter
    Key --> KeyFilter

    style 用户认证 fill:#dbeafe,stroke:#3b82f6
    style API 认证 fill:#fef3c7,stroke:#f59e0b
```

## SMS 验证码登录

### 登录流程

```mermaid theme={null}
sequenceDiagram
    participant U as 用户
    participant W as Web (Next.js)
    participant C as Core API
    participant R as Redis
    participant SMS as 短信服务

    U->>W: 输入手机号，点击获取验证码
    W->>C: POST /api/auth/sms/sendCode {phone}
    C->>R: 检查发送频率限制
    C->>SMS: 发送验证码
    C->>R: SET sms:code:{phone} → code (TTL 5min)
    C-->>W: 发送成功

    U->>W: 输入验证码，点击登录
    W->>C: POST /api/auth/login {phone, code}
    C->>R: GET sms:code:{phone}
    C->>C: 验证码匹配 → 查询/创建用户
    C->>C: signToken(userId, JWT_SECRET)
    C->>R: SET auth:token:{token} → {userId, phone}
    C->>R: SET auth:user-token:{userId} → token
    C-->>W: {token, user}

    W->>W: POST /api/auth/session → 设置 Cookie
    W-->>U: 登录成功
```

### 关键设计

* **无密码** — 手机号即身份，验证码即凭证，无需记忆密码
* **自动注册** — 首次登录自动创建用户和积分账户
* **频率限制** — Redis 控制同一手机号的发送间隔，防止滥用

## JWT Token 管理

### Token 签发

使用 `@profy/auth` 包（基于 `jose` 库）签发 HS256 JWT：

```typescript theme={null}
signToken(userId: string, secret: string, expiresIn?: string)
// 默认有效期 7 天
// Payload: { sub: userId, iat, exp }
```

### Redis 会话存储

Token 不仅作为无状态 JWT 存在，同时在 Redis 中维护一份会话记录：

| Redis Key                  | Value             | TTL         |
| -------------------------- | ----------------- | ----------- |
| `auth:token:{token}`       | `{userId, phone}` | 86400s（24h） |
| `auth:user-token:{userId}` | `token`           | 86400s（24h） |

每次 API 请求命中 Auth Filter 时，TTL 会被刷新（滑动过期），活跃用户不会被踢出。

### 单设备策略

每次登录生成新 Token 并写入 `auth:user-token:{userId}`，覆盖旧值。旧 Token 在 Redis 中的 `auth:token:{oldToken}` 仍然存在直到 TTL 过期，但 Auth Filter 会通过校验 `auth:user-token:{userId}` 确保只有最新 Token 有效。

## Auth Filter 中间件

Auth Filter 应用于所有 `/api/*` 路由，是 Core API 的安全屏障。

### 处理流程

```mermaid theme={null}
flowchart TD
    Req[请求到达] --> WL{在白名单中？}
    WL -->|是| Pass[直接放行]
    WL -->|否| Extract[提取 Token]
    Extract --> HasToken{Token 存在？}
    HasToken -->|否| Reject[401 未授权]
    HasToken -->|是| RedisCheck{Redis 查询}
    RedisCheck -->|命中| InjectUser[注入用户上下文<br/>刷新 TTL]
    RedisCheck -->|未命中| JWTVerify{JWT 验证}
    JWTVerify -->|有效| InjectUser
    JWTVerify -->|无效| Reject
    InjectUser --> Next[继续处理]
```

### 白名单路由

以下路由跳过认证，直接放行：

| 路由                       | 说明           |
| ------------------------ | ------------ |
| `/api/auth/login`        | 登录接口         |
| `/api/auth/sms/sendCode` | 发送验证码        |
| `/api/health`            | 健康检查         |
| `/api/payment/notify/*`  | 支付回调（微信/支付宝） |
| `/api/market/*`          | 市场公开浏览接口     |

### Token 提取优先级

1. `Authorization: Bearer {token}` 请求头
2. Cookie 中的 `token` 字段

## API Key 认证

针对第三方系统集成，Profy 提供基于 API Key 的认证方式，用于 `/openapi/*` 路由。

### API Key 管理

| 方法   | 路径                        | 说明            |
| ---- | ------------------------- | ------------- |
| POST | `/api/user/apiKey/create` | 创建 API Key    |
| POST | `/api/user/apiKey/list`   | 列出用户的 API Key |
| POST | `/api/user/apiKey/delete` | 删除 API Key    |

API Key 以 `sk_` 前缀标识，存储在 Redis `openapi:key:{key}` 中用于快速验证。

### OpenAPI 路由

| 方法   | 路径                         | 说明   |
| ---- | -------------------------- | ---- |
| GET  | `/openapi/credit/balance`  | 查询余额 |
| POST | `/openapi/credit/consume`  | 消费积分 |
| POST | `/openapi/credit/preCheck` | 预检余额 |

### Key Filter 处理

```mermaid theme={null}
flowchart LR
    Req[/openapi/* 请求] --> Extract["提取 X-Api-Key<br/>或 Authorization"]
    Extract --> Validate{Redis 验证<br/>openapi:key:*}
    Validate -->|有效| Inject[注入用户上下文]
    Validate -->|无效| Reject[401]
```

## Web 端 Cookie 管理

Next.js 通过两个专用端点管理认证 Cookie：

| 方法     | 路径                  | 说明                               |
| ------ | ------------------- | -------------------------------- |
| POST   | `/api/auth/session` | 设置 `profy_token` HttpOnly Cookie |
| DELETE | `/api/auth/session` | 清除 Cookie（登出）                    |

Expert 路由（`/claw/*`）受 `proxy.ts` 中间件保护，无 Cookie 自动重定向到登录页。

## Expert Auth 桥接

Expert 的 Next.js API 路由需要获取当前用户身份，通过 `getAuthUserId()` 实现：

1. 从 `profy_token` Cookie 或 Bearer Header 提取 Token
2. 内存缓存查询（TTL 5 分钟）
3. 缓存未命中则调用 Core `/api/auth/userinfo` 验证
4. 返回 `userId` 供 Expert API 使用

## 安全实践

| 措施                  | 说明                   |
| ------------------- | -------------------- |
| **HS256 JWT**       | 对称签名，服务端独占密钥         |
| **Redis 会话**        | 支持即时失效，不依赖 JWT 过期    |
| **滑动过期**            | 活跃用户 Token TTL 自动延长  |
| **单设备**             | 新登录自动踢出旧设备           |
| **HttpOnly Cookie** | 防止 XSS 窃取 Token      |
| **频率限制**            | SMS 发送间隔限制防刷         |
| **内网隔离**            | `/internal/*` 仅限内网访问 |

## 相关链接

<CardGroup cols={2}>
  <Card title="系统架构" icon="sitemap" href="/zh/documentation/architecture/overview">
    Auth Filter 在请求链路中的位置
  </Card>

  <Card title="金融支付" icon="credit-card" href="/zh/documentation/finance">
    OpenAPI 积分操作需要 API Key 认证
  </Card>
</CardGroup>
