# 统一认证平台(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` | 服务器内部错误 |