认证 API
用户认证和授权相关的 API 端点。
端点概览
| 方法 | 端点 | 描述 | 认证 |
|---|---|---|---|
| POST | /auth/login | 用户登录 | ❌ |
| POST | /auth/register | 用户注册 | ❌ |
| POST | /auth/logout | 用户登出 | ✅ |
| POST | /auth/github/authorize-url | 获取 GitHub OAuth URL | ❌ |
| GET | /auth/github/callback | GitHub OAuth 回调 | ❌ |
| POST | /auth/password/forgot | 忘记密码 | ❌ |
| POST | /auth/password/reset | 重置密码 | ❌ |
| GET | /me | 获取当前用户信息 | ✅ |
| PATCH | /me | 更新当前用户信息 | ✅ |
| POST | /me/password | 修改密码 | ✅ |
用户登录
POST /api/v1/auth/login
使用邮箱/用户名和密码登录。
请求
http
POST /api/v1/auth/login
Content-Type: application/json
{
"identifier": "user@example.com",
"password": "your_password",
"turnstile_token": "optional_cloudflare_token"
}参数说明:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
identifier | string | ✅ | 邮箱或用户名 |
password | string | ✅ | 密码(至少8位) |
turnstile_token | string | ❌ | Cloudflare Turnstile 验证令牌 |
响应
成功 (200 OK):
json
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_at": "2024-01-02T12:00:00Z",
"user": {
"user_id": "550e8400-e29b-41d4-a716-446655440000",
"username": "johndoe",
"email": "user@example.com",
"role": "user",
"status": "active",
"last_login_at": "2024-01-01T12:00:00Z"
}
}错误响应:
400 Bad Request: 参数格式错误401 Unauthorized: 用户名或密码错误403 Forbidden: 账户被暂停
示例
typescript
const login = async (identifier: string, password: string) => {
const response = await fetch('/api/v1/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ identifier, password }),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error);
}
const data = await response.json();
localStorage.setItem('token', data.token);
return data;
};用户注册
POST /api/v1/auth/register
注册新用户账户。
请求
http
POST /api/v1/auth/register
Content-Type: application/json
{
"username": "johndoe",
"email": "user@example.com",
"password": "securepassword123",
"turnstile_token": "optional_cloudflare_token"
}参数说明:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
username | string | ✅ | 用户名(3-30位,字母数字下划线) |
email | string | ✅ | 邮箱地址 |
password | string | ✅ | 密码(至少8位) |
turnstile_token | string | ❌ | Cloudflare Turnstile 验证令牌 |
验证规则:
- 用户名: 3-30 个字符,只能包含字母、数字、下划线和连字符
- 邮箱: 有效的邮箱格式
- 密码: 至少 8 个字符
响应
成功 (201 Created):
json
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_at": "2024-01-02T12:00:00Z",
"user": {
"user_id": "550e8400-e29b-41d4-a716-446655440000",
"username": "johndoe",
"email": "user@example.com",
"role": "user",
"status": "active"
}
}错误响应:
400 Bad Request: 参数验证失败409 Conflict: 用户名或邮箱已存在
示例
typescript
const register = async (username: string, email: string, password: string) => {
const response = await fetch('/api/v1/auth/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, email, password }),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error);
}
const data = await response.json();
localStorage.setItem('token', data.token);
return data;
};用户登出
POST /api/v1/auth/logout
登出当前用户(清除服务端会话)。
请求
http
POST /api/v1/auth/logout
Authorization: Bearer <token>响应
成功 (200 OK):
json
{
"message": "Logged out successfully"
}示例
typescript
const logout = async () => {
const token = localStorage.getItem('token');
await fetch('/api/v1/auth/logout', {
method: 'POST',
headers: { 'Authorization': `Bearer ${token}` },
});
localStorage.removeItem('token');
};GitHub OAuth
POST /api/v1/auth/github/authorize-url
获取 GitHub OAuth 授权 URL。
请求
http
POST /api/v1/auth/github/authorize-url
Content-Type: application/json
{
"login": "suggested_username",
"turnstile_token": "optional_cloudflare_token"
}参数说明:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
login | string | ❌ | 建议的 GitHub 用户名 |
turnstile_token | string | ❌ | Cloudflare Turnstile 验证令牌 |
响应
成功 (200 OK):
json
{
"authorize_url": "https://github.com/login/oauth/authorize?client_id=xxx&state=yyy"
}使用流程
- 调用此端点获取授权 URL
- 重定向用户到该 URL
- 用户授权后,GitHub 会重定向回 callback URL
- 前端从 callback URL 获取 code 和 state
- 调用 callback 端点完成登录
示例
typescript
const initiateGithubLogin = async () => {
const response = await fetch('/api/v1/auth/github/authorize-url', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({}),
});
const data = await response.json();
window.location.href = data.authorize_url;
};GET /api/v1/auth/github/callback
处理 GitHub OAuth 回调。
请求
http
GET /api/v1/auth/github/callback?code=xxx&state=yyy参数说明:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
code | string | ✅ | GitHub 授权码 |
state | string | ✅ | CSRF 防护状态码 |
响应
成功 (200 OK):
json
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_at": "2024-01-02T12:00:00Z",
"user": {
"user_id": "550e8400-e29b-41d4-a716-446655440000",
"username": "johndoe",
"email": "user@example.com",
"role": "user",
"status": "active"
}
}说明:
- 如果 GitHub 用户首次登录,会自动创建账户
- 邮箱从 GitHub 获取
- 用户名从 GitHub 登录名生成
密码管理
POST /api/v1/auth/password/forgot
请求密码重置邮件。
请求
http
POST /api/v1/auth/password/forgot
Content-Type: application/json
{
"email": "user@example.com",
"turnstile_token": "optional_cloudflare_token"
}参数说明:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
email | string | ✅ | 注册邮箱 |
turnstile_token | string | ❌ | Cloudflare Turnstile 验证令牌 |
响应
成功 (200 OK):
json
{
"accepted": true,
"debug_reset_token": "optional_dev_only_token"
}说明:
- 无论邮箱是否存在都返回成功(防止邮箱枚举)
- 开发环境可能返回
debug_reset_token用于测试 - 重置链接有效期 10 分钟
示例
typescript
const forgotPassword = async (email: string) => {
const response = await fetch('/api/v1/auth/password/forgot', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email }),
});
if (!response.ok) {
throw new Error('Failed to send reset email');
}
return response.json();
};POST /api/v1/auth/password/reset
使用重置令牌重置密码。
请求
http
POST /api/v1/auth/password/reset
Content-Type: application/json
{
"token": "reset_token_from_email",
"new_password": "new_secure_password"
}参数说明:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
token | string | ✅ | 重置令牌(来自邮件) |
new_password | string | ✅ | 新密码(至少8位) |
响应
成功 (200 OK):
json
{
"message": "Password reset successfully"
}错误响应:
400 Bad Request: 令牌无效或已过期400 Bad Request: 密码不符合要求
示例
typescript
const resetPassword = async (token: string, newPassword: string) => {
const response = await fetch('/api/v1/auth/password/reset', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token, new_password: newPassword }),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error);
}
};用户信息管理
GET /api/v1/me
获取当前登录用户的信息。
请求
http
GET /api/v1/me
Authorization: Bearer <token>响应
成功 (200 OK):
json
{
"user_id": "550e8400-e29b-41d4-a716-446655440000",
"username": "johndoe",
"email": "user@example.com",
"role": "user",
"status": "active",
"last_login_at": "2024-01-01T12:00:00Z"
}错误响应:
401 Unauthorized: Token 无效或已过期
示例
typescript
const getCurrentUser = async () => {
const token = localStorage.getItem('token');
const response = await fetch('/api/v1/me', {
headers: { 'Authorization': `Bearer ${token}` },
});
if (!response.ok) {
throw new Error('Failed to fetch user info');
}
return response.json();
};PATCH /api/v1/me
更新当前用户信息。
请求
http
PATCH /api/v1/me
Authorization: Bearer <token>
Content-Type: application/json
{
"username": "new_username",
"email": "new_email@example.com"
}参数说明:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
username | string | ❌ | 新用户名 |
email | string | ❌ | 新邮箱 |
说明:
- 至少提供一个字段
- 新用户名和邮箱不能与已有用户冲突
响应
成功 (200 OK):
json
{
"user_id": "550e8400-e29b-41d4-a716-446655440000",
"username": "new_username",
"email": "new_email@example.com",
"role": "user",
"status": "active"
}错误响应:
400 Bad Request: 参数验证失败409 Conflict: 用户名或邮箱已被使用
示例
typescript
const updateProfile = async (updates: { username?: string; email?: string }) => {
const token = localStorage.getItem('token');
const response = await fetch('/api/v1/me', {
method: 'PATCH',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(updates),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error);
}
return response.json();
};POST /api/v1/me/password
修改当前用户密码。
请求
http
POST /api/v1/me/password
Authorization: Bearer <token>
Content-Type: application/json
{
"current_password": "old_password",
"new_password": "new_password"
}参数说明:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
current_password | string | ✅ | 当前密码 |
new_password | string | ✅ | 新密码(至少8位) |
响应
成功 (200 OK):
json
{
"message": "Password changed successfully"
}错误响应:
400 Bad Request: 当前密码错误400 Bad Request: 新密码不符合要求
示例
typescript
const changePassword = async (currentPassword: string, newPassword: string) => {
const token = localStorage.getItem('token');
const response = await fetch('/api/v1/me/password', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
current_password: currentPassword,
new_password: newPassword,
}),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error);
}
};安全注意事项
密码安全
- 后端使用 Argon2id 算法哈希密码
- 密码强度要求:至少 8 个字符
- 建议使用包含大小写字母、数字和特殊字符的密码
Token 安全
- JWT Token 有效期为 24 小时
- Token 应存储在 localStorage 或 sessionStorage
- 不要在 URL 中传递 Token
- HTTPS 环境下使用
CSRF 防护
- GitHub OAuth 使用 state 参数防止 CSRF
- 敏感操作建议二次验证
防暴力破解
- Cloudflare Turnstile 人机验证(可选)
- 建议实施登录频率限制