99 lines
3.3 KiB
Markdown
99 lines
3.3 KiB
Markdown
|
|
# API Key 认证流程
|
|||
|
|
|
|||
|
|
## 概述
|
|||
|
|
|
|||
|
|
API Key 认证是系统支持的两种认证方式之一(另一种是 JWT)。客户端通过 `X-API-Key` 请求头传递明文 key,中间件负责解析、校验并将认证用户注入到 request attribute 中,供下游中间件和控制器使用。
|
|||
|
|
|
|||
|
|
## API Key 格式
|
|||
|
|
|
|||
|
|
明文格式:`{user_id}#{token}`
|
|||
|
|
|
|||
|
|
- `user_id`:用户 ID(整数)
|
|||
|
|
- `#`:分隔符
|
|||
|
|
- `token`:64 字符十六进制随机串(`bin2hex(random_bytes(32))`)
|
|||
|
|
|
|||
|
|
示例:`42#a3b5c7d9e1f2...`(完整 token 为 64 字符)
|
|||
|
|
|
|||
|
|
数据库仅存储 token 的 SHA-256 哈希值(`key_hash`),不存储明文。明文仅在生成时返回给用户一次。
|
|||
|
|
|
|||
|
|
## 认证流程图
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
flowchart TD
|
|||
|
|
A["请求进入 AuthMiddleware"] --> B{"有 Bearer Token?"}
|
|||
|
|
B -- 是 --> JWT["JWT 认证路径"]
|
|||
|
|
B -- 否 --> C{"有 X-API-Key 头?"}
|
|||
|
|
C -- 否 --> R401_NO["401 未授权,请先登录"]
|
|||
|
|
C -- 是 --> D["解析明文 key"]
|
|||
|
|
|
|||
|
|
D --> E{"包含 # 分隔符?"}
|
|||
|
|
E -- 否 --> R401_FMT["401 API Key 无效或已过期"]
|
|||
|
|
E -- 是 --> F["拆分 user_id 和 token sha256(token) → key_hash"]
|
|||
|
|
|
|||
|
|
F --> G["查询 api_keys 表WHERE (user_id, key_hash)AND 未过期"]
|
|||
|
|
G --> H{"记录存在?"}
|
|||
|
|
H -- 否 --> R401_KEY["401 API Key 无效或已过期"]
|
|||
|
|
H -- 是 --> I{"key.enabled?"}
|
|||
|
|
|
|||
|
|
I -- false --> R403_KEY["403 该 API Key 已被禁用"]
|
|||
|
|
I -- true --> J["加载关联 user"]
|
|||
|
|
J --> K{"user 存在且user.status = 1?"}
|
|||
|
|
|
|||
|
|
K -- 否 --> R403_USER["403 账号已被禁用"]
|
|||
|
|
K -- 是 --> L{"user.api_key_enabled?"}
|
|||
|
|
|
|||
|
|
L -- false --> R403_FEAT["403 API Key 功能未启用"]
|
|||
|
|
L -- 是 --> M["认证成功"]
|
|||
|
|
|
|||
|
|
M --> N["更新 last_used_at"]
|
|||
|
|
N --> O["注入 request attribute\nauth_user → User\nauth_type → 'api_key'"]
|
|||
|
|
O --> P["写入协程 Context"]
|
|||
|
|
P --> Q["放行到下游 handler"]
|
|||
|
|
|
|||
|
|
style R401_NO fill:#fdd,stroke:#c00
|
|||
|
|
style R401_FMT fill:#fdd,stroke:#c00
|
|||
|
|
style R401_KEY fill:#fdd,stroke:#c00
|
|||
|
|
style R403_KEY fill:#fdd,stroke:#c00
|
|||
|
|
style R403_USER fill:#fdd,stroke:#c00
|
|||
|
|
style R403_FEAT fill:#fdd,stroke:#c00
|
|||
|
|
style M fill:#dfd,stroke:#0a0
|
|||
|
|
style JWT fill:#def,stroke:#08c
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 关键 SQL
|
|||
|
|
|
|||
|
|
解析阶段的数据库查询走 `(user_id, key_hash)` 联合索引,单次精确命中:
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
SELECT * FROM api_keys
|
|||
|
|
WHERE user_id = {user_id}
|
|||
|
|
AND key_hash = {hash}
|
|||
|
|
AND (expires_at IS NULL OR expires_at > NOW())
|
|||
|
|
LIMIT 1
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 数据查询汇总
|
|||
|
|
|
|||
|
|
整个认证流程涉及 2 次数据库查询 + 1 次写入:
|
|||
|
|
|
|||
|
|
| 操作 | SQL | 说明 |
|
|||
|
|
|------|-----|------|
|
|||
|
|
| 查找 API Key | `SELECT FROM api_keys WHERE (user_id, key_hash)` | 联合索引精确匹配 |
|
|||
|
|
| 加载关联用户 | `SELECT FROM users WHERE id = {user_id}` | `$api_key->user` 懒加载 |
|
|||
|
|
| 更新使用时间 | `UPDATE api_keys SET last_used_at = NOW()` | 记录最后使用时间 |
|
|||
|
|
|
|||
|
|
## 下游消费
|
|||
|
|
|
|||
|
|
认证完成后,下游代码通过以下方式获取认证用户:
|
|||
|
|
|
|||
|
|
- **中间件**:`$request->getAttribute('auth_user')` 或从 Context 读取
|
|||
|
|
- **控制器**:`$this->getAuthUser()`(AbstractController 提供,含 JWT guard 兜底)
|
|||
|
|
|
|||
|
|
## 相关文件
|
|||
|
|
|
|||
|
|
| 文件 | 职责 |
|
|||
|
|
|------|------|
|
|||
|
|
| `app/Middleware/AuthMiddleware.php` | 认证入口,执行校验链 |
|
|||
|
|
| `app/Model/ApiKey.php` | key 解析、DB 查询、格式定义 |
|
|||
|
|
| `app/Controller/AbstractController.php` | `getAuthUser()` 统一用户获取 |
|