Files
SatoNano/docs/auth-api.md
2026-01-06 23:49:23 +08:00

705 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 用户认证系统 API 文档
## 概述
本文档描述 SatoNano 云服务综合管理平台的用户认证系统 API。
### 基础信息
- **Base URL**: `http://localhost:8000/api/v1`
- **认证方式**: Bearer Token (JWT)
- **Content-Type**: `application/json`
- **支持登录方式**: 用户名密码 / OAuth2 (Linux.do)
### 统一响应格式
所有 API 响应遵循统一格式:
```json
{
"success": true,
"message": "操作成功",
"data": { ... }
}
```
错误响应:
```json
{
"success": false,
"message": "错误描述",
"code": "ERROR_CODE",
"details": { ... }
}
```
---
## 认证接口
### 1. 用户注册
创建新用户账户。
**请求**
```
POST /auth/register
```
**请求体**
| 字段 | 类型 | 必填 | 描述 |
|------|------|------|------|
| username | string | ✅ | 用户名3-32位字母开头只允许字母、数字、下划线 |
| password | string | ✅ | 密码8-128位需包含大小写字母和数字 |
| email | string | ❌ | 邮箱地址 |
| nickname | string | ❌ | 昵称最长64位 |
**示例请求**
```json
{
"username": "john_doe",
"password": "SecurePass123",
"email": "john@example.com",
"nickname": "John"
}
```
**成功响应** `201 Created`
```json
{
"success": true,
"message": "注册成功",
"data": {
"id": "875cd9b4-3504-455d-9290-b6d1ba6b56e0",
"username": "john_doe",
"email": "john@example.com",
"nickname": "John",
"avatar_url": null,
"bio": null,
"is_active": true,
"created_at": "2026-01-05T13:39:07.653138",
"last_login_at": null
}
}
```
**错误响应**
| 状态码 | 说明 |
|--------|------|
| 409 | 用户名或邮箱已被注册 |
| 422 | 请求数据验证失败 |
---
### 2. 用户登录
使用用户名/邮箱和密码登录,获取访问令牌。
**请求**
```
POST /auth/login
```
**请求体**
| 字段 | 类型 | 必填 | 描述 |
|------|------|------|------|
| username | string | ✅ | 用户名或邮箱 |
| password | string | ✅ | 密码 |
**示例请求**
```json
{
"username": "john_doe",
"password": "SecurePass123"
}
```
**成功响应** `200 OK`
```json
{
"success": true,
"message": "登录成功",
"data": {
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"refresh_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 1800
}
}
```
**错误响应**
| 状态码 | 说明 |
|--------|------|
| 401 | 用户名或密码错误 |
| 403 | 账户已被禁用 |
---
### 3. 刷新令牌
使用刷新令牌获取新的访问令牌。
**请求**
```
POST /auth/refresh
```
**请求体**
| 字段 | 类型 | 必填 | 描述 |
|------|------|------|------|
| refresh_token | string | ✅ | 刷新令牌 |
**示例请求**
```json
{
"refresh_token": "eyJhbGciOiJIUzI1NiIs..."
}
```
**成功响应** `200 OK`
```json
{
"success": true,
"message": "刷新成功",
"data": {
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"refresh_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 1800
}
}
```
**错误响应**
| 状态码 | 说明 |
|--------|------|
| 401 | 令牌无效或已过期 |
| 403 | 账户已被禁用 |
---
### 4. 用户退出
退出登录。客户端应删除本地存储的令牌。
**请求**
```
POST /auth/logout
```
**请求头**
```
Authorization: Bearer <access_token>
```
**成功响应** `200 OK`
```json
{
"success": true,
"message": "退出成功",
"data": null
}
```
---
### 5. 修改密码
修改当前用户的密码。
**请求**
```
POST /auth/change-password
```
**请求头**
```
Authorization: Bearer <access_token>
```
**请求体**
| 字段 | 类型 | 必填 | 描述 |
|------|------|------|------|
| current_password | string | ✅ | 当前密码 |
| new_password | string | ✅ | 新密码8-128位 |
**示例请求**
```json
{
"current_password": "SecurePass123",
"new_password": "NewSecurePass456"
}
```
**成功响应** `200 OK`
```json
{
"success": true,
"message": "密码修改成功",
"data": null
}
```
**错误响应**
| 状态码 | 说明 |
|--------|------|
| 400 | 当前密码错误 / 新密码不符合要求 |
| 401 | 未认证 |
---
## OAuth2 接口
SatoNano 支持通过 Linux.do 平台进行 OAuth2 第三方登录。
### 特性
- **主备端点自动切换**:当首选 OAuth2 端点不可达时,自动回退到备用端点
- **状态码验证**:使用 state 参数防止 CSRF 攻击
- **自动用户创建**:首次登录自动创建本地用户账户
### 1. 获取授权 URL
获取 OAuth2 授权页面 URL用于重定向用户到第三方平台。
**请求**
```
GET /auth/oauth2/authorize
```
**成功响应** `200 OK`
```json
{
"success": true,
"message": "请重定向到授权 URL",
"data": {
"authorize_url": "https://connect.linux.do/oauth2/authorize?client_id=xxx&redirect_uri=xxx&response_type=code&state=xxx&scope=read",
"state": "random-state-string"
}
}
```
**使用流程**
1. 前端调用此接口获取 `authorize_url`
2. 将用户重定向到 `authorize_url`
3. 用户在 Linux.do 完成授权
4. Linux.do 重定向回应用的回调 URL
---
### 2. OAuth2 回调
处理 OAuth2 授权回调,完成登录流程。
**请求**
```
GET /auth/oauth2/callback?code=xxx&state=xxx
```
**查询参数**
| 参数 | 类型 | 必填 | 描述 |
|------|------|------|------|
| code | string | ✅ | OAuth2 授权码 |
| state | string | ✅ | 状态码(防 CSRF |
**成功响应** `200 OK`
```json
{
"success": true,
"message": "登录成功",
"data": {
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"refresh_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 1800,
"is_new_user": false
}
}
```
**响应字段说明**
| 字段 | 说明 |
|------|------|
| is_new_user | `true` 表示首次登录,已自动创建账户 |
**错误响应**
| 状态码 | 说明 |
|--------|------|
| 400 | 无效的状态码(可能是 CSRF 攻击或状态已过期) |
| 401 | OAuth2 认证失败(授权码无效等) |
| 503 | OAuth2 未配置或服务不可用 |
---
### OAuth2 登录流程图
```
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 前端应用 │ │ SatoNano │ │ Linux.do │
└─────┬───────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
│ 1. GET /auth/oauth2/authorize │
│─────────────────────────> │
│ │ │
│ 2. 返回 authorize_url + state │
│<───────────────────────── │
│ │ │
│ 3. 重定向用户到 authorize_url │
│──────────────────────────────────────────────────>
│ │ │
│ │ 4. 用户授权 │
│ │<───────────────────────│
│ │ │
│ 5. 重定向回 callback?code=xxx&state=xxx │
│<──────────────────────────────────────────────────
│ │ │
│ 6. GET /auth/oauth2/callback │
│─────────────────────────> │
│ │ │
│ │ 7. 用 code 换取 token │
│ │───────────────────────>│
│ │ │
│ │ 8. 返回 access_token │
│ │<───────────────────────│
│ │ │
│ │ 9. 获取用户信息 │
│ │───────────────────────>│
│ │ │
│ │ 10. 返回 user info │
│ │<───────────────────────│
│ │ │
│ 11. 返回 JWT 令牌 │ │
│<───────────────────────── │
│ │ │
```
---
## 用户接口
### 1. 获取当前用户信息
获取当前登录用户的详细信息。
**请求**
```
GET /users/me
```
**请求头**
```
Authorization: Bearer <access_token>
```
**成功响应** `200 OK`
```json
{
"success": true,
"message": "操作成功",
"data": {
"id": "875cd9b4-3504-455d-9290-b6d1ba6b56e0",
"username": "john_doe",
"email": "john@example.com",
"nickname": "John",
"avatar_url": null,
"bio": null,
"is_active": true,
"created_at": "2026-01-05T13:39:07.653138",
"last_login_at": "2026-01-05T13:39:19.028376"
}
}
```
---
### 2. 更新当前用户信息
更新当前登录用户的资料。
**请求**
```
PATCH /users/me
```
**请求头**
```
Authorization: Bearer <access_token>
```
**请求体**(所有字段可选)
| 字段 | 类型 | 描述 |
|------|------|------|
| nickname | string | 昵称最长64位 |
| email | string | 邮箱地址 |
| avatar_url | string | 头像 URL最长512位 |
| bio | string | 个人简介最长500位 |
**示例请求**
```json
{
"nickname": "Johnny",
"bio": "Hello, World!"
}
```
**成功响应** `200 OK`
```json
{
"success": true,
"message": "更新成功",
"data": {
"id": "875cd9b4-3504-455d-9290-b6d1ba6b56e0",
"username": "john_doe",
"email": "john@example.com",
"nickname": "Johnny",
"avatar_url": null,
"bio": "Hello, World!",
"is_active": true,
"created_at": "2026-01-05T13:39:07.653138",
"last_login_at": "2026-01-05T13:39:19.028376"
}
}
```
**错误响应**
| 状态码 | 说明 |
|--------|------|
| 409 | 邮箱已被其他用户使用 |
---
### 3. 获取指定用户信息
获取指定用户的信息。
**请求**
```
GET /users/{user_id}
```
**请求头**
```
Authorization: Bearer <access_token>
```
**路径参数**
| 参数 | 类型 | 描述 |
|------|------|------|
| user_id | string | 用户 UUID |
**成功响应** `200 OK`
```json
{
"success": true,
"message": "操作成功",
"data": {
"id": "875cd9b4-3504-455d-9290-b6d1ba6b56e0",
"username": "john_doe",
"email": "john@example.com",
"nickname": "Johnny",
"avatar_url": null,
"bio": "Hello, World!",
"is_active": true,
"created_at": "2026-01-05T13:39:07.653138",
"last_login_at": "2026-01-05T13:39:19.028376"
}
}
```
**错误响应**
| 状态码 | 说明 |
|--------|------|
| 404 | 用户不存在 |
---
## 错误码说明
| 错误码 | HTTP 状态 | 说明 |
|--------|-----------|------|
| AUTHENTICATION_ERROR | 401 | 认证失败 |
| INVALID_CREDENTIALS | 401 | 用户名或密码错误 |
| TOKEN_ERROR | 401 | 令牌无效 |
| TOKEN_EXPIRED | 401 | 令牌已过期 |
| OAUTH2_ENDPOINT_ERROR | 401 | OAuth2 服务不可用 |
| OAUTH2_STATE_ERROR | 400 | OAuth2 状态码无效 |
| OAUTH2_TOKEN_ERROR | 401 | OAuth2 令牌获取失败 |
| OAUTH2_USERINFO_ERROR | 401 | OAuth2 用户信息获取失败 |
| AUTHORIZATION_ERROR | 403 | 权限不足 |
| USER_DISABLED | 403 | 账户已被禁用 |
| RESOURCE_NOT_FOUND | 404 | 资源不存在 |
| USER_ALREADY_EXISTS | 409 | 用户已存在 |
| VALIDATION_ERROR | 422 | 数据验证失败 |
| PASSWORD_VALIDATION_ERROR | 422 | 密码不符合要求 |
---
## 密码策略
默认密码要求:
- 长度8-128 位
- 必须包含至少一个大写字母
- 必须包含至少一个小写字母
- 必须包含至少一个数字
可通过环境变量配置:
```bash
PASSWORD_MIN_LENGTH=8
PASSWORD_MAX_LENGTH=128
PASSWORD_REQUIRE_UPPERCASE=true
PASSWORD_REQUIRE_LOWERCASE=true
PASSWORD_REQUIRE_DIGIT=true
PASSWORD_REQUIRE_SPECIAL=false
```
---
## 令牌说明
### Access Token
- 用于 API 认证
- 默认有效期30 分钟
- 通过 `Authorization: Bearer <token>` 请求头传递
### Refresh Token
- 用于获取新的 Access Token
- 默认有效期7 天
- 仅用于 `/auth/refresh` 接口
### JWT Payload 结构
**Access Token:**
```json
{
"sub": "user-uuid",
"iat": 1767620359,
"exp": 1767622159,
"type": "access",
"username": "john_doe",
"is_superuser": false,
"oauth_provider": null
}
```
> OAuth2 登录的用户 `oauth_provider` 字段为 `"linuxdo"`
**Refresh Token:**
```json
{
"sub": "user-uuid",
"iat": 1767620359,
"exp": 1768225159,
"type": "refresh"
}
```
---
## OAuth2 配置说明
### Linux.do OAuth2 端点
| 端点 | URL |
|------|-----|
| authorize | `https://connect.linux.do/oauth2/authorize` |
| token | `https://connect.linux.do/oauth2/token` |
| userinfo | `https://connect.linux.do/api/user` |
### 在 `config.yaml` 中配置
```yaml
# OAuth2 配置 (Linux.do)
oauth2_client_id: your_client_id
oauth2_client_secret: your_client_secret
oauth2_callback_path: /api/v1/auth/oauth2/callback
# 首选端点
oauth2_authorize_endpoint: https://connect.linux.do/oauth2/authorize
oauth2_token_endpoint: https://connect.linux.do/oauth2/token
oauth2_user_info_endpoint: https://connect.linux.do/api/user
# 备用端点(首选不可达时自动回退)
oauth2_authorize_endpoint_reserve: https://connect.linuxdo.org/oauth2/authorize
oauth2_token_endpoint_reserve: https://connect.linuxdo.org/oauth2/token
oauth2_user_info_endpoint_reserve: https://connect.linuxdo.org/api/user
# 请求超时(秒)
oauth2_request_timeout: 10
```
### Linux.do 返回的用户信息
```json
{
"id": 1,
"username": "neo",
"name": "Neo",
"active": true,
"trust_level": 4,
"email": "u1@linux.do",
"avatar_url": "https://linux.do/xxxx",
"silenced": false
}
```
### 主备端点切换逻辑
1. 首先尝试首选端点(`connect.linux.do`
2. 如果首选端点超时、连接失败或返回 5xx 错误,自动切换到备用端点(`connect.linuxdo.org`
3. 如果备用端点也失败,抛出 `OAUTH2_ENDPOINT_ERROR` 错误