提供基本前后端骨架
This commit is contained in:
704
docs/auth-api.md
Normal file
704
docs/auth-api.md
Normal file
@@ -0,0 +1,704 @@
|
||||
# 用户认证系统 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` 错误
|
||||
|
||||
Reference in New Issue
Block a user