feat(auth): 添加 SECTL Auth OAuth 登录功能

- 新增 OAuth 登录组件和回调页面
- 集成 Tauri 深度链接插件处理 OAuth 回调
- 添加账户设置页面显示登录信息
- 更新 CI 配置添加 OAuth 环境变量
- 扩展 API 接口支持 OAuth 相关操作
- 添加国际化支持
This commit is contained in:
Yukino_fox
2026-03-29 19:30:27 +08:00
parent 3f3bfbf284
commit 7a59572f11
33 changed files with 8477 additions and 200 deletions
+482
View File
@@ -0,0 +1,482 @@
# 平台接入指南
本文档介绍如何将第三方平台与 SECTL Auth 集成,实现统一认证和远程退登功能。
## 目录
- [概述](#概述)
- [快速开始](#快速开始)
- [管理后台功能](#管理后台功能)
- [OAuth 集成](#oauth-集成)
- [远程退登](#远程退登)
- [代码示例](#代码示例)
- [常见问题](#常见问题)
## 概述
SECTL Auth 提供以下核心功能:
1. **统一认证** - 用户使用 SECTL Auth 账号登录第三方平台
2. **远程退登** - 用户在 SECTL Auth 中心退出后,平台自动退出
3. **用户管理** - 查看已授权用户、统计信息等
### 工作流程
```
┌─────────────┐ 登录请求 ┌─────────────┐
│ 用户 │ ─────────────> │ SECTL Auth │
│ │ │ │
│ 平台A │ <───────────── │ 认证中心 │
│ │ Access Token │ │
└──────┬──────┘ └──────┬──────┘
│ │
│ 远程退登通知 │
│ <─────────────────────────── │
│ (Realtime) │
▼ ▼
自动退出登录 用户点击退出
```
## 快速开始
### 1. 注册平台
在 SECTL Auth 平台管理页面 ([PlatformManagementView.vue](../../src/views/dashboard/manage/PlatformManagementView.vue)) 申请接入,获取:
| 参数 | 说明 | 示例 |
| -------------- | ------------ | ------------------------------------- |
| platform_id | 平台唯一标识 | `pf_AHmIhAiptyLFxk8x` |
| platform_token | 平台认证密钥 | `sk_UyMHuFf6oKXh8ZcHSb7lfOSgzumoOeg1` |
| callback_url | 回调地址 | `http://localhost:5000/callback` |
### 2. 平台状态说明
平台在管理后台有以下状态:
| 状态 | 说明 | 可操作 |
| -------- | ------ | -------- |
| active | 运行中 | 正常授权 |
| pending | 审核中 | 等待审核 |
| disabled | 已停用 | 无法授权 |
### 3. 查看统计信息
在平台管理页面可以查看:
- **总平台数** - 已创建的平台数量
- **运行中** - 活跃平台数量
- **审核中** - 待审核申请数量
- **总用户数** - 授权用户总数
## 管理后台功能
### 平台列表 ([PlatformManagementView.vue](../../src/views/dashboard/manage/PlatformManagementView.vue))
平台管理页面提供以下功能:
#### 1. 平台卡片展示
每个平台显示:
- 平台名称和描述
- 状态标签(运行中/审核中/已停用)
- 创建时间
- 授权用户数
- 管理按钮
#### 2. 搜索和过滤
- 按名称搜索平台
- 按状态过滤
- 分页展示
#### 3. 快捷操作
- **新建平台** - 跳转到申请页面
- **我的申请** - 查看自己的平台申请
- **审核申请** - 审核其他用户的申请(需 >=18 权限)
### 平台详情 ([PlatformDetailView.vue](../../src/views/dashboard/detail/PlatformDetailView.vue))
查看平台详细信息:
#### 基本信息
- 平台 ID (Client ID)
- 平台密钥 (Client Secret)
- 回调地址
- 创建时间
#### 安全设置
- Token 过期时间配置
- 密钥重置
- 回调地址修改
#### 用户统计
- 授权用户列表
- 登录时间
- 最后活跃时间
### 平台申请 ([PlatformApplyView.vue](../../src/views/dashboard/form/PlatformApplyView.vue))
申请创建新平台:
```javascript
// 申请参数
{
name: "我的应用", // 平台名称
description: "应用描述", // 平台描述
callback_url: "http://..." // 回调地址
}
```
申请后状态为 `pending`,等待管理员审核。
### 申请审核 ([PlatformApplicationsView.vue](../../src/views/dashboard/manage/PlatformApplicationsView.vue))
管理员审核平台申请:
- 查看申请详情
- 批准申请(状态变为 `active`
- 拒绝申请(状态变为 `disabled`
## OAuth 集成
### 授权流程
#### 1. 引导用户到授权页面
```
GET https://sectl.top/oauth/authorize
```
**参数:**
| 参数名 | 必填 | 说明 |
| ------------- | ---- | ------------- |
| client_id | ✅ | 平台 ID |
| redirect_uri | ✅ | 回调地址 |
| response_type | ✅ | 固定值 `code` |
**示例:**
```
https://sectl.top/oauth/authorize?client_id=pf_xxx&redirect_uri=http://localhost:5000/callback&response_type=code
```
#### 2. 处理回调
用户授权后,会重定向到:
```
{callback_url}?code={authorization_code}
```
#### 3. 换取访问令牌
```http
POST https://sectl.top/api/oauth/token
Content-Type: application/json
{
"grant_type": "authorization_code",
"code": "",
"client_id": "pf_xxx",
"client_secret": "sk_xxx",
"redirect_uri": "http://localhost:5000/callback"
}
```
**响应:**
```json
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"refresh_token": "xxx",
"token_type": "Bearer",
"expires_in": 3600
}
```
#### 4. 获取用户信息
```http
GET https://sectl.top/api/oauth/userinfo
Authorization: Bearer {access_token}
```
**响应:**
```json
{
"user_id": "69bd422960018cf4d0e5",
"email": "user@example.com",
"name": "张三",
"github_username": "zhangsan"
}
```
## 远程退登
### 实现原理
1. 用户在 SECTL Auth 点击退出登录
2. 系统创建 `logout_events` 文档
3. 第三方应用通过 Appwrite Realtime 订阅该集合
4. 收到匹配的事件后执行本地退出
### 集成步骤
#### 1. 安装 Appwrite SDK
**C#**
```bash
dotnet add package Appwrite
```
**Python**
```bash
pip install appwrite
```
#### 2. 配置 Realtime 订阅
**C# 示例:**
```csharp
using Appwrite;
using Appwrite.Services;
var client = new Client()
.SetEndpoint("https://cloud.appwrite.io/v1")
.SetProject("your-project-id");
var realtime = new Realtime(client);
// 订阅 logout_events 集合
realtime.Subscribe("databases.{databaseId}.collections.logout_events.documents",
callback: (message) => {
var logoutEvent = message.Payload;
if (logoutEvent.UserId == currentUserId) {
// 执行本地退出
LocalLogout();
}
});
```
**Python 示例:**
```python
from appwrite.client import Client
from appwrite.services.realtime import Realtime
client = Client()
client.set_endpoint('https://cloud.appwrite.io/v1')
client.set_project('your-project-id')
realtime = Realtime(client)
def on_logout_event(message):
event = message['payload']
if event['user_id'] == current_user_id:
# 执行本地退出
local_logout()
# 订阅 logout_events 集合
realtime.subscribe(
'databases.{database_id}.collections.logout_events.documents',
callback=on_logout_event
)
```
### 数据结构
**logout_events 集合字段:**
| 字段 | 类型 | 说明 |
| ----------- | ------- | ------------------ |
| user_id | String | 退出登录的用户ID |
| platform_id | String | 特定平台ID(可选) |
| global | Boolean | 是否全局退出 |
| created_at | String | 创建时间 |
## 代码示例
### C# 完整示例
详见 [csharp-example](./csharp-example/) 目录。
```csharp
// SectlAuthClient.cs
public class SectlAuthClient
{
private readonly string _platformId;
private readonly string _platformSecret;
private readonly string _callbackUrl;
private readonly HttpClient _httpClient;
public SectlAuthClient(string platformId, string platformSecret, string callbackUrl)
{
_platformId = platformId;
_platformSecret = platformSecret;
_callbackUrl = callbackUrl;
_httpClient = new HttpClient();
}
// 获取授权 URL
public string GetAuthorizationUrl()
{
return $"https://sectl.top/oauth/authorize?" +
$"client_id={_platformId}&" +
$"redirect_uri={Uri.EscapeDataString(_callbackUrl)}&" +
$"response_type=code";
}
// 换取访问令牌
public async Task<TokenResponse> ExchangeCodeAsync(string code)
{
var request = new
{
grant_type = "authorization_code",
code = code,
client_id = _platformId,
client_secret = _platformSecret,
redirect_uri = _callbackUrl
};
var response = await _httpClient.PostAsJsonAsync(
"https://sectl.top/api/oauth/token", request);
return await response.Content.ReadFromJsonAsync<TokenResponse>();
}
// 获取用户信息
public async Task<UserInfo> GetUserInfoAsync(string accessToken)
{
_httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", accessToken);
var response = await _httpClient.GetAsync(
"https://sectl.top/api/oauth/userinfo");
return await response.Content.ReadFromJsonAsync<UserInfo>();
}
}
```
### Python 完整示例
详见 [python-example](./python-example/) 目录。
```python
# sectl_auth/client.py
import requests
class SectlAuthClient:
BASE_URL = "https://sectl.top"
AUTH_URL = "https://sectl.top"
def __init__(self, platform_id: str, platform_secret: str, callback_url: str):
self.platform_id = platform_id
self.platform_secret = platform_secret
self.callback_url = callback_url
def get_authorization_url(self) -> str:
"""获取授权 URL"""
return (
f"{self.AUTH_URL}/oauth/authorize?"
f"client_id={self.platform_id}&"
f"redirect_uri={self.callback_url}&"
f"response_type=code"
)
def exchange_code(self, code: str) -> dict:
"""用授权码换取访问令牌"""
response = requests.post(
f"{self.BASE_URL}/api/oauth/token",
json={
"grant_type": "authorization_code",
"code": code,
"client_id": self.platform_id,
"client_secret": self.platform_secret,
"redirect_uri": self.callback_url
}
)
response.raise_for_status()
return response.json()
def get_user_info(self, access_token: str) -> dict:
"""获取用户信息"""
response = requests.get(
f"{self.BASE_URL}/api/oauth/userinfo",
headers={"Authorization": f"Bearer {access_token}"}
)
response.raise_for_status()
return response.json()
```
## 常见问题
### Q: 如何获取 platform_id 和 platform_token
A: 在 SECTL Auth 平台管理页面申请创建平台,审核通过后会获得:
- platform_id (Client ID)
- platform_token (Client Secret)
### Q: 授权码有效期多久?
A: 授权码有效期为 10 分钟,且只能使用一次。
### Q: Access Token 过期后怎么办?
A: 使用 refresh_token 换取新的 access_token
```http
POST /api/oauth/token
{
"grant_type": "refresh_token",
"refresh_token": "your-refresh-token",
"client_id": "your-platform-id",
"client_secret": "your-platform-secret"
}
```
### Q: 如何配置 Token 过期时间?
A: 在平台设置页面 ([PlatformSettingsView.vue](../../src/views/dashboard/other/PlatformSettingsView.vue)) 可以配置:
- access_token 过期时间(默认 3600 秒)
- 设置为 -1 表示永不过期
### Q: 远程退登不生效怎么办?
A: 检查以下几点:
1. 是否正确订阅了 `logout_events` 集合
2. 检查 user_id 是否匹配
3. 确认 Appwrite Realtime 连接正常
4. 查看浏览器控制台是否有错误
### Q: 平台状态显示"审核中"怎么办?
A: 新创建的平台需要管理员审核。审核通过后状态会变为 `active`
## 相关页面
- [PlatformManagementView.vue](../../src/views/dashboard/manage/PlatformManagementView.vue) - 平台管理
- [PlatformDetailView.vue](../../src/views/dashboard/detail/PlatformDetailView.vue) - 平台详情
- [PlatformApplyView.vue](../../src/views/dashboard/form/PlatformApplyView.vue) - 申请平台
- [PlatformApplicationsView.vue](../../src/views/dashboard/manage/PlatformApplicationsView.vue) - 审核申请
- [PlatformSettingsView.vue](../../src/views/dashboard/other/PlatformSettingsView.vue) - 平台设置
- [LoginPlatformsView.vue](../../src/views/dashboard/list/LoginPlatformsView.vue) - 登录平台列表
## 参考文档
- [OAuth 2.0 流程](./OAUTH_FLOW.MD)
- [API 参考](./API_REFERENCE.md)
- [故障排查](../oauth-flow/TROUBLESHOOTING.md)