ソースを参照

统一认证平台接入文档

lingmin_package@163.com 2 週間 前
コミット
763ac45cf8

+ 681 - 0
文档/统一认证平台接入流程及API接口文档.md

@@ -0,0 +1,681 @@
+# 统一认证平台(LQAI-middle-platform)接入流程及 API 接口文档
+
+> **参考样本**: 样本中心(LQAdminPlatform)已实现的统一认证接入方案
+> **统一认证平台地址**: `http://192.168.92.61:8200`
+> **统一认证平台前端**: `http://192.168.92.61:9200`
+
+---
+
+## 一、系统架构概览
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│                统一认证平台 (LQAI-middle-platform)            │
+│                    端口: 8200 (后端) / 9200 (前端)            │
+│                                                             │
+│  功能:                                                       │
+│  • 用户注册 / 登录 / 密码管理                                  │
+│  • 应用管理 (子应用注册、client_id/secret 生成)                │
+│  • OAuth2 授权码流程                                          │
+│  • RBAC 角色权限管理                                          │
+│  • JWT Token 签发与验证                                       │
+│  • 子应用工作台(点击子应用图标触发 SSO 跳转)                  │
+│                                                             │
+│  数据存储: MySQL (lq_ai_middle_platform) + Redis             │
+└──────────────────────┬──────────────────────────────────────┘
+                       │ OAuth2 / HTTP
+                       │
+        ┌──────────────┼──────────────┐
+        │              │              │
+   ┌────▼────┐  ┌─────▼─────┐  ┌────▼────┐
+   │样本中心  │  │Agent平台   │  │ 标注平台 │  ... 其他子系统
+   │(8000)   │  │ (8002)    │  │ (9003)  │
+   └─────────┘  └───────────┘  └─────────┘
+```
+
+### 角色定位
+
+| 角色 | 说明 |
+|------|------|
+| **统一认证平台** (LQAI-middle-platform) | OAuth2 授权服务器(Authorization Server),同时也是一个 SSO 中心 |
+| **子系统** (如样本中心 LQAdminPlatform) | OAuth2 客户端(Client),同时自身也是一个小型 OAuth2 服务器,可以为下游系统提供授权 |
+| **子系统前端** | 负责 SSO 回调处理和 Token 交换 |
+
+---
+
+## 二、SSO 单点登录接入流程
+
+### 2.1 整体流程图
+
+```
+用户在统一认证平台工作台点击子系统图标
+            │
+            ▼
+① 统一认证平台后端生成 OAuth2 授权码 code
+   POST /auth/sso-redirect  →  返回重定向URL: {frontend_url}/auth/callback?code=xxx
+            │
+            ▼
+② 302 重定向到子系统前端 /auth/callback?code=xxx
+            │
+            ▼
+③ 子系统前端 POST /api/oauth/exchange-code { "code": "xxx" }
+            │
+            ▼
+④ 子系统后端与统一认证平台进行后端到后端通信:
+   4a. POST {SSO_BASE_URL}/oauth/token  →  换取 SSO access_token
+   4b. GET  {SSO_BASE_URL}/oauth/userinfo →  获取用户信息+角色
+            │
+            ▼
+⑤ 子系统后端同步用户到本地数据库(查找或创建用户、同步角色)
+            │
+            ▼
+⑥ 子系统后端签发本地 JWT,返回 { token, refresh_token, user }
+            │
+            ▼
+⑦ 子系统前端保存 Token,跳转首页
+```
+
+### 2.2 详细步骤说明
+
+#### Step 1: 统一认证平台生成授权码
+
+当用户在统一认证平台前端工作台的"应用列表"中点击某个子系统图标时:
+
+- 统一认证平台后端调用 `POST /auth/sso-redirect` 接口
+- 后端为当前登录用户生成一个 OAuth2 授权码(authorization code),存入 Redis,有效期 10 分钟
+- 返回子系统的回调 URL,格式为 `{REDIRECT_URI}?code={auth_code}`
+
+#### Step 2: 重定向到子系统前端
+
+- 统一认证平台前端通过 302 重定向将用户引导至子系统前端的回调页面
+- URL 格式: `http://localhost:3000/auth/callback?code=xxxxxx`
+
+#### Step 3: 子系统前端调用换码接口
+
+子系统前端从 URL 参数中提取 `code`,调用子系统后端的换码接口:
+
+```typescript
+// 示例代码
+const code = new URLSearchParams(window.location.search).get('code');
+const response = await fetch('/api/oauth/exchange-code', {
+  method: 'POST',
+  headers: { 'Content-Type': 'application/json' },
+  body: JSON.stringify({ code })
+});
+const result = await response.json();
+// result.data.token       → 本地 JWT access_token
+// result.data.refresh_token → 刷新令牌
+// result.data.user         → 用户信息(含角色)
+```
+
+#### Step 4-5: 子系统后端与统一认证平台交互 + 同步用户
+
+子系统后端收到 `code` 后执行 `_sso_exchange_code` 核心流程:
+
+1. **用 code 换 SSO access_token** — 调用统一认证平台的 `/oauth/token` 端点
+2. **获取用户信息** — 调用统一认证平台的 `/oauth/userinfo` 端点
+3. **同步用户** — 在本地数据库查找或创建用户,同步角色信息
+
+#### Step 6-7: 签发本地 JWT 并返回
+
+- 子系统后端用自己的 `SECRET_KEY` 签发本地 JWT
+- 返回给前端 `{ token, refresh_token, user }`
+- 前端保存 Token 到 localStorage/sessionStorage,跳转首页
+
+---
+
+## 三、子系统接入前准备
+
+### 3.1 在统一认证平台注册子应用
+
+通过统一认证平台的系统管理模块(或数据库直接插入)在 `t_sys_app` 表中注册子系统:
+
+| 字段 | 说明 | 示例值 |
+|------|------|--------|
+| `name` | 子系统名称 | "样本中心" |
+| `app_key` | 客户端 ID(client_id),32 位随机字符串 | `WviiGL8KQE20tQhmhQPQhhJ5QpFK51F6` |
+| `app_secret` | 客户端密钥(client_secret),64 位随机字符串 | `9WXP88hEHJiHRSiUdmx7ip5oQPzY0bnJNsEswQoO4sk6juCplyJTcnAiZsv7e3lJ` |
+| `redirect_uris` | 允许的重定向 URI(JSON 数组) | `["http://localhost:3000/auth/callback"]` |
+| `scope` | 授权范围(JSON 数组) | `["profile", "email"]` |
+| `is_active` | 是否启用 | `1` |
+| `is_trusted` | 是否信任(跳过授权确认页) | `1` |
+| `home_url` | 子系统首页 URL | `http://localhost:3000` |
+| `icon_url` | 子系统图标 URL | 可选 |
+
+### 3.2 子系统侧配置
+
+在子系统的配置文件中添加 SSO 配置段:
+
+```ini
+[sso]
+SSO_BASE_URL=http://192.168.92.61:8200
+CLIENT_ID=WviiGL8KQE20tQhmhQPQhhJ5QpFK51F6
+CLIENT_SECRET=9WXP88hEHJiHRSiUdmx7ip5oQPzY0bnJNsEswQoO4sk6juCplyJTcnAiZsv7e3lJ
+REDIRECT_URI=http://localhost:3000/auth/callback
+FRONTEND_URL=http://localhost:3000
+SCOPE=email
+SSO_LOGOUT_REDIRECT_URL=http://192.168.92.61:9200/login
+```
+
+> **注意**: `CLIENT_ID` 和 `CLIENT_SECRET` 必须与统一认证平台 `t_sys_app` 表中注册的 `app_key` 和 `app_secret` 一致。
+
+### 3.3 角色映射
+
+子系统需要确保本地 `t_sys_role` 表中存在与统一认证平台对应的角色。样本中心的角色包括:
+
+| 统一认证平台角色 code | 说明 |
+|----------------------|------|
+| `super_admin` | 超级管理员 |
+| `sam_sys_admin` | 样本中心系统管理员 |
+| `sam_data_operator` | 样本中心数据操作员 |
+
+统一认证平台 `/oauth/userinfo` 返回的 `roles` 字段格式为对象数组:
+
+```json
+{
+  "roles": [
+    { "name": "超级管理员", "code": "super_admin" },
+    { "name": "样本中心系统管理员", "code": "sam_sys_admin" }
+  ]
+}
+```
+
+子系统从每个对象中提取 `code` 字段进行角色匹配和本地同步。
+
+---
+
+## 四、统一认证平台核心 API 接口
+
+以下接口由 **统一认证平台(LQAI-middle-platform,端口 8200)** 提供,子系统需要调用。
+
+### 4.1 获取 SSO 授权 URL
+
+> 供子系统前端构建"跳转到统一认证平台登录"的链接
+
+- **接口**: `GET /oauth/authorize`
+- **认证**: 不需要
+
+| 参数 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| `response_type` | string | 是 | 固定值 `code` |
+| `client_id` | string | 是 | 子应用的 app_key |
+| `redirect_uri` | string | 是 | 回调地址(必须在 t_sys_app 中注册过) |
+| `scope` | string | 否 | 授权范围,空格分隔,默认 `profile` |
+| `state` | string | 否 | 防 CSRF 状态参数 |
+
+**响应**: 302 重定向(如果用户已登录则重定向到 `redirect_uri?code=xxx`,未登录则重定向到登录页面)
+
+### 4.2 获取授权码 URL(SSO 免登)
+
+> 统一认证平台内部使用,当用户点击子系统图标时调用
+
+- **接口**: `POST /auth/sso-redirect`
+- **认证**: 需要用户已登录统一认证平台
+- **请求体**:
+
+```json
+{
+  "client_id": "WviiGL8KQE20tQhmhQPQhhJ5QpFK51F6",
+  "redirect_uri": "http://localhost:3000/auth/callback"
+}
+```
+
+- **响应**:
+
+```json
+{
+  "code": "000000",
+  "message": "success",
+  "data": {
+    "redirect_url": "http://localhost:3000/auth/callback?code=xxxxxx"
+  }
+}
+```
+
+### 4.3 令牌交换端点
+
+> **子系统后端调用此接口,用授权码换取 SSO access_token**
+
+- **接口**: `POST /oauth/token`
+- **Content-Type**: `application/x-www-form-urlencoded`
+- **认证**: 不需要
+
+| 参数 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| `grant_type` | string | 是 | 固定值 `authorization_code` |
+| `code` | string | 是 | 授权码 |
+| `redirect_uri` | string | 是 | 与 Step 1 中使用的 redirect_uri 一致 |
+| `client_id` | string | 是 | 子应用的 app_key |
+| `client_secret` | string | 是 | 子应用的 app_secret |
+
+**成功响应** (200):
+
+```json
+{
+  "access_token": "eyJhbGciOiJIUzI1NiIs...",
+  "token_type": "Bearer",
+  "expires_in": 7200,
+  "refresh_token": "xxxxxxxxxxxxxxxxxxxx",
+  "scope": "profile email"
+}
+```
+
+**失败响应**:
+
+```json
+{
+  "error": "invalid_grant",
+  "error_description": "授权码无效"
+}
+```
+
+| error 值 | 说明 |
+|----------|------|
+| `invalid_request` | 缺少必填参数 |
+| `unsupported_grant_type` | grant_type 不支持 |
+| `invalid_client` | client_id 或 client_secret 错误 |
+| `invalid_grant` | 授权码无效/已使用/过期,或 redirect_uri 不匹配 |
+
+### 4.4 获取用户信息
+
+> **子系统后端调用此接口,获取当前登录用户的详细信息**
+
+- **接口**: `GET /oauth/userinfo`
+- **认证**: Bearer Token(使用 4.3 返回的 access_token)
+
+**请求头**:
+
+```
+Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
+```
+
+**成功响应** (200):
+
+```json
+{
+  "sub": "user_xxx",
+  "username": "admin",
+  "email": "admin@example.com",
+  "avatar_url": "https://...",
+  "real_name": "管理员",
+  "company": "四川路桥",
+  "department": "技术部",
+  "position": "工程师",
+  "roles": [
+    { "name": "超级管理员", "code": "super_admin" },
+    { "name": "样本中心系统管理员", "code": "sam_sys_admin" }
+  ]
+}
+```
+
+> 返回的字段根据 token 的 scope 过滤:`profile` scope 返回基本信息,`email` scope 返回邮箱,`phone` scope 返回手机号。
+
+**失败响应**:
+
+```json
+{
+  "error": "invalid_token",
+  "error_description": "User not found or inactive"
+}
+```
+
+---
+
+## 五、子系统侧需要实现的 API 接口
+
+以下是子系统(如样本中心)自身需要实现的接口,用于完成 SSO 接入。
+
+### 5.1 授权码交换接口(核心免登接口)
+
+- **接口**: `POST /api/oauth/exchange-code`
+- **Content-Type**: `application/json`
+- **认证**: 不需要
+- **用途**: 前端收到 SSO 回调的 code 后,调用此接口换取本地 JWT
+
+**请求体**:
+
+```json
+{
+  "code": "xxxxxx"
+}
+```
+
+**成功响应**:
+
+```json
+{
+  "code": "000000",
+  "message": "登录成功",
+  "data": {
+    "token": "eyJhbGciOiJIUzI1NiIs...",
+    "refresh_token": "xxxxxxxxxxxxxxxxxxxx",
+    "user": {
+      "id": "user_xxx",
+      "username": "admin",
+      "email": "admin@example.com",
+      "phone": "138xxxx",
+      "is_superuser": true,
+      "is_active": true,
+      "roles": ["super_admin", "sam_sys_admin"]
+    }
+  }
+}
+```
+
+**失败响应**:
+
+```json
+{
+  "code": "100001",
+  "message": "缺少授权码",
+  "data": null
+}
+```
+
+```json
+{
+  "code": "500001",
+  "message": "登录失败: 获取令牌失败",
+  "data": null
+}
+```
+
+### 5.2 获取 SSO 授权 URL
+
+- **接口**: `GET /auth/sso/authorize`
+- **认证**: 不需要
+
+| 参数 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| `redirect` | bool | 否 | 为 true 时直接 302 重定向到 SSO 授权页 |
+
+**响应** (redirect=false):
+
+```json
+{
+  "code": "000000",
+  "message": "获取授权URL成功",
+  "data": {
+    "authorize_url": "http://192.168.92.61:8200/oauth/authorize?client_id=xxx&redirect_uri=xxx&response_type=code&scope=email"
+  }
+}
+```
+
+### 5.3 SSO 回调端点(旧流程,后端 302 重定向方式)
+
+- **接口**: `GET /auth/callback`
+- **认证**: 不需要
+
+| 参数 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| `code` | string | 是 | SSO 授权码 |
+| `error` | string | 否 | SSO 返回的错误码 |
+| `error_description` | string | 否 | 错误描述 |
+| `state` | string | 否 | 状态参数 |
+
+**响应**: 302 重定向到 `前端URL/oauth/callback?token=xxx&refresh_token=xxx`
+
+> **注意**: 这是旧流程,建议使用 `POST /api/oauth/exchange-code` 的新流程(前端直接调用 API 换码,不依赖后端 302 重定向)。
+
+---
+
+## 六、子系统侧的其他认证相关 API
+
+除了 SSO 接入外,子系统还需要提供标准认证接口供前端使用。
+
+### 6.1 本地密码登录
+
+- **接口**: `POST /api/v1/auth/login`
+- **Content-Type**: `application/json`
+
+**请求体**:
+
+```json
+{
+  "username": "admin",
+  "password": "Admin123456",
+  "remember_me": false
+}
+```
+
+**响应**:
+
+```json
+{
+  "code": "000000",
+  "message": "登录成功",
+  "data": {
+    "access_token": "eyJ...",
+    "refresh_token": "xxx...",
+    "token_type": "Bearer",
+    "expires_in": 1200
+  }
+}
+```
+
+### 6.2 刷新 Token
+
+- **接口**: `POST /api/v1/auth/refresh`
+- **请求体**: `{ "refresh_token": "xxx" }`
+
+### 6.3 登出
+
+- **接口**: `POST /api/v1/auth/logout`
+- **请求体**: `{ "token": "xxx", "refresh_token": "xxx" }`
+- **响应** (含 SSO 登出重定向 URL):
+
+```json
+{
+  "code": "000000",
+  "message": "登出成功",
+  "data": {
+    "sso_logout_url": "http://192.168.92.61:9200/login"
+  }
+}
+```
+
+### 6.4 获取当前用户信息
+
+- **接口**: `GET /api/v1/auth/userinfo` 或 `GET /api/v1/auth/me`
+- **认证**: Bearer Token
+- **响应**:
+
+```json
+{
+  "code": "000000",
+  "data": {
+    "id": "user_xxx",
+    "username": "admin",
+    "email": "admin@example.com",
+    "phone": "138xxxx",
+    "roles": ["super_admin"],
+    "permissions": []
+  }
+}
+```
+
+### 6.5 获取验证码
+
+- **接口**: `GET /api/v1/auth/captcha`
+- **认证**: 不需要
+
+---
+
+## 七、统一认证平台的 OAuth2 服务端 API
+
+统一认证平台自身也是一个 OAuth2 授权服务器,可以为下游子系统提供 OAuth2 服务。
+
+### 7.1 应用管理(系统管理员操作)
+
+通过统一认证平台的系统管理模块管理子应用,相关接口前缀 `/api/v1`:
+
+| 方法 | 路径 | 说明 |
+|------|------|------|
+| `GET` | `/api/v1/system/app/list` | 获取应用列表 |
+| `POST` | `/api/v1/system/app/create` | 创建应用(自动生成 app_key/app_secret) |
+| `PUT` | `/api/v1/system/app/update/{id}` | 更新应用信息 |
+| `DELETE` | `/api/v1/system/app/delete/{id}` | 删除应用 |
+| `POST` | `/api/v1/system/app/reset_secret/{id}` | 重置应用密钥 |
+| `GET` | `/api/v1/system/app/accessible` | 获取当前用户可访问的应用列表 |
+
+### 7.2 角色管理
+
+| 方法 | 路径 | 说明 |
+|------|------|------|
+| `GET` | `/api/v1/system/role/list` | 获取角色列表 |
+| `POST` | `/api/v1/system/role/create` | 创建角色 |
+| `PUT` | `/api/v1/system/role/update/{id}` | 更新角色 |
+| `DELETE` | `/api/v1/system/role/delete/{id}` | 删除角色 |
+| `GET` | `/api/external/v1/system/role/user_list/{role_code}` | 按角色 code 查询用户列表 |
+
+---
+
+## 八、Token 机制说明
+
+### 8.1 统一认证平台侧 Token
+
+| Token 类型 | 有效期 | 存储方式 | 说明 |
+|-----------|--------|---------|------|
+| Access Token | 30 分钟(默认)| Redis (`auth:access:{user_id}:{token}`) | 访问令牌,含用户身份和角色 |
+| Refresh Token | 30 天 | Redis (`auth:refresh:{user_id}:{token}`) | 刷新令牌,用于换取新 Access Token |
+| OAuth Access Token | 120 分钟 | Redis (`auth:oauth_access:{client_id}:{token}`) | OAuth2 流程中颁发给子应用的令牌 |
+| OAuth Authorization Code | 10 分钟 | Redis (`auth:oauth_code:{code}`) | 一次性授权码,使用后即失效 |
+
+### 8.2 子系统侧 Token
+
+| Token 类型 | 有效期 | 存储方式 | 说明 |
+|-----------|--------|---------|------|
+| Access Token | 20 分钟 | MySQL + Redis | 子系统本地登录令牌 |
+| Refresh Token | 24 小时 | MySQL + Redis | 刷新令牌 |
+
+### 8.3 滑动过期机制
+
+子系统实现了 JWT Token 的滑动过期机制:
+
+- 当 Token 使用时间超过其总生命周期的 **50%** 时,中间件会自动生成新 Token
+- 新 Token 通过响应头 `X-New-Token` 返回给前端
+- 前端检测到 `X-New-Token` 后替换本地旧 Token
+- 无需前端主动刷新即可保持登录状态
+
+---
+
+## 九、前端接入参考代码
+
+### 9.1 SSO 免登流程(推荐方式)
+
+```typescript
+// 1. 统一认证平台重定向到子系统前端,URL 携带 code 参数
+// 例如: http://localhost:3000/auth/callback?code=xxxxxx
+
+// 2. 在回调页面中提取 code 并调用换码接口
+const code = new URLSearchParams(window.location.search).get('code');
+
+if (code) {
+  const response = await fetch('/api/oauth/exchange-code', {
+    method: 'POST',
+    headers: { 'Content-Type': 'application/json' },
+    body: JSON.stringify({ code })
+  });
+  const result = await response.json();
+
+  if (result.code === '000000') {
+    // 3. 保存 Token
+    localStorage.setItem('token', result.data.token);
+    localStorage.setItem('refresh_token', result.data.refresh_token);
+    localStorage.setItem('user', JSON.stringify(result.data.user));
+
+    // 4. 跳转到首页
+    router.push('/home');
+  } else {
+    // 登录失败,跳回登录页
+    router.push(`/login?error=${result.message}`);
+  }
+}
+```
+
+### 9.2 前端请求拦截器(携带 Token)
+
+```typescript
+// Axios 请求拦截器示例
+axios.interceptors.request.use(config => {
+  const token = localStorage.getItem('token');
+  if (token) {
+    config.headers.Authorization = `Bearer ${token}`;
+  }
+  return config;
+});
+
+// 响应拦截器处理 Token 刷新
+axios.interceptors.response.use(
+  response => {
+    const newToken = response.headers['x-new-token'];
+    if (newToken) {
+      localStorage.setItem('token', newToken);
+    }
+    return response;
+  },
+  error => {
+    if (error.response?.status === 401) {
+      // Token 过期,清除并跳转登录
+      localStorage.removeItem('token');
+      localStorage.removeItem('refresh_token');
+      router.push('/login');
+    }
+    return Promise.reject(error);
+  }
+);
+```
+
+---
+
+## 十、接入 Checklist
+
+- [ ] 在统一认证平台注册子应用,获取 `client_id` 和 `client_secret`
+- [ ] 在子应用配置文件中添加 `[sso]` 配置段
+- [ ] 子应用后端实现 `POST /api/oauth/exchange-code` 换码接口
+- [ ] 子应用前端实现回调页面 `/auth/callback`,从 URL 提取 code 并调换码接口
+- [ ] 子应用本地数据库初始化角色表(`t_sys_role`),确保角色 code 与统一认证平台一致
+- [ ] 子应用配置 CORS 允许前端域名访问
+- [ ] 配置 Token 滑动过期中间件
+- [ ] 配置登出时返回 SSO 登出重定向 URL
+- [ ] 测试完整 SSO 流程:统一认证平台登录 → 点击子系统 → 自动免登进入子系统
+
+---
+
+## 十一、错误码说明
+
+### 统一响应格式
+
+所有接口使用统一的响应格式:
+
+```json
+{
+  "code": "000000",
+  "message": "success",
+  "data": { ... },
+  "timestamp": "2026-05-10T12:00:00Z"
+}
+```
+
+### 业务错误码
+
+| 错误码 | 说明 |
+|--------|------|
+| `000000` | 成功 |
+| `100001` | 缺少授权码 |
+| `400001` | SSO 授权码无效 |
+| `400002` | SSO 用户信息格式异常 |
+| `500001` | 服务器内部错误 |
+
+### OAuth2 标准错误
+
+| error | 说明 |
+|-------|------|
+| `invalid_request` | 请求参数不完整或格式错误 |
+| `invalid_client` | client_id 或 client_secret 错误 |
+| `invalid_grant` | 授权码无效、已使用、过期或 redirect_uri 不匹配 |
+| `invalid_scope` | 请求的 scope 超出应用注册范围 |
+| `unsupported_response_type` | response_type 不支持(仅支持 code) |
+| `unsupported_grant_type` | grant_type 不支持(仅支持 authorization_code) |
+| `access_denied` | 用户拒绝授权 |
+| `server_error` | 服务器内部错误 |

+ 4 - 0
文档/项目文档.md

@@ -67,6 +67,10 @@ LQAI-middle-platform-front
 | 15 | 智能体平台 | Agent | agt | 业务员 | agt_business | 1、已经发布智能的使用 | agt_user_003 | 业务推广员 | |
 
 
+超级管理员(super_admin Admin@123!)
+
+
+
  - LQAI-middle-platform 后端 
    - 基于如上角色管理清单,根据角色表定义生成相关角色表SQL脚本
    - 基于如上用户清单,根据用户表定义生成相关用户表SQL脚本