# 用户认证系统 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 ``` **成功响应** `200 OK` ```json { "success": true, "message": "退出成功", "data": null } ``` --- ### 5. 修改密码 修改当前用户的密码。 **请求** ``` POST /auth/change-password ``` **请求头** ``` Authorization: Bearer ``` **请求体** | 字段 | 类型 | 必填 | 描述 | |------|------|------|------| | 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 ``` **成功响应** `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 ``` **请求体**(所有字段可选) | 字段 | 类型 | 描述 | |------|------|------| | 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 ``` **路径参数** | 参数 | 类型 | 描述 | |------|------|------| | 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 ` 请求头传递 ### 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` 错误