参考样本: 样本中心(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 交换 |
用户在统一认证平台工作台点击子系统图标
│
▼
① 统一认证平台后端生成 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,跳转首页
当用户在统一认证平台前端工作台的"应用列表"中点击某个子系统图标时:
POST /auth/sso-redirect 接口{REDIRECT_URI}?code={auth_code}http://localhost:3000/auth/callback?code=xxxxxx子系统前端从 URL 参数中提取 code,调用子系统后端的换码接口:
// 示例代码
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 → 用户信息(含角色)
子系统后端收到 code 后执行 _sso_exchange_code 核心流程:
/oauth/token 端点/oauth/userinfo 端点SECRET_KEY 签发本地 JWT{ token, refresh_token, user }通过统一认证平台的系统管理模块(或数据库直接插入)在 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 | 可选 |
在子系统的配置文件中添加 SSO 配置段:
[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一致。
子系统需要确保本地 t_sys_role 表中存在与统一认证平台对应的角色。样本中心的角色包括:
| 统一认证平台角色 code | 说明 |
|---|---|
super_admin |
超级管理员 |
sam_sys_admin |
样本中心系统管理员 |
sam_data_operator |
样本中心数据操作员 |
统一认证平台 /oauth/userinfo 返回的 roles 字段格式为对象数组:
{
"roles": [
{ "name": "超级管理员", "code": "super_admin" },
{ "name": "样本中心系统管理员", "code": "sam_sys_admin" }
]
}
子系统从每个对象中提取 code 字段进行角色匹配和本地同步。
以下接口由 统一认证平台(LQAI-middle-platform,端口 8200) 提供,子系统需要调用。
供子系统前端构建"跳转到统一认证平台登录"的链接
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,未登录则重定向到登录页面)
统一认证平台内部使用,当用户点击子系统图标时调用
POST /auth/sso-redirect请求体:
{
"client_id": "WviiGL8KQE20tQhmhQPQhhJ5QpFK51F6",
"redirect_uri": "http://localhost:3000/auth/callback"
}
响应:
{
"code": "000000",
"message": "success",
"data": {
"redirect_url": "http://localhost:3000/auth/callback?code=xxxxxx"
}
}
子系统后端调用此接口,用授权码换取 SSO access_token
POST /oauth/tokenapplication/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):
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 7200,
"refresh_token": "xxxxxxxxxxxxxxxxxxxx",
"scope": "profile email"
}
失败响应:
{
"error": "invalid_grant",
"error_description": "授权码无效"
}
| error 值 | 说明 |
|---|---|
invalid_request |
缺少必填参数 |
unsupported_grant_type |
grant_type 不支持 |
invalid_client |
client_id 或 client_secret 错误 |
invalid_grant |
授权码无效/已使用/过期,或 redirect_uri 不匹配 |
子系统后端调用此接口,获取当前登录用户的详细信息
GET /oauth/userinfo请求头:
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
成功响应 (200):
{
"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 过滤:
profilescope 返回基本信息,phonescope 返回手机号。
失败响应:
{
"error": "invalid_token",
"error_description": "User not found or inactive"
}
以下是子系统(如样本中心)自身需要实现的接口,用于完成 SSO 接入。
POST /api/oauth/exchange-codeapplication/json请求体:
{
"code": "xxxxxx"
}
成功响应:
{
"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"]
}
}
}
失败响应:
{
"code": "100001",
"message": "缺少授权码",
"data": null
}
{
"code": "500001",
"message": "登录失败: 获取令牌失败",
"data": null
}
GET /auth/sso/authorize| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
redirect |
bool | 否 | 为 true 时直接 302 重定向到 SSO 授权页 |
响应 (redirect=false):
{
"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"
}
}
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 重定向)。
除了 SSO 接入外,子系统还需要提供标准认证接口供前端使用。
POST /api/v1/auth/loginapplication/json请求体:
{
"username": "admin",
"password": "Admin123456",
"remember_me": false
}
响应:
{
"code": "000000",
"message": "登录成功",
"data": {
"access_token": "eyJ...",
"refresh_token": "xxx...",
"token_type": "Bearer",
"expires_in": 1200
}
}
POST /api/v1/auth/refresh{ "refresh_token": "xxx" }POST /api/v1/auth/logout{ "token": "xxx", "refresh_token": "xxx" }响应 (含 SSO 登出重定向 URL):
{
"code": "000000",
"message": "登出成功",
"data": {
"sso_logout_url": "http://192.168.92.61:9200/login"
}
}
GET /api/v1/auth/userinfo 或 GET /api/v1/auth/me响应:
{
"code": "000000",
"data": {
"id": "user_xxx",
"username": "admin",
"email": "admin@example.com",
"phone": "138xxxx",
"roles": ["super_admin"],
"permissions": []
}
}
GET /api/v1/auth/captcha统一认证平台自身也是一个 OAuth2 授权服务器,可以为下游子系统提供 OAuth2 服务。
通过统一认证平台的系统管理模块管理子应用,相关接口前缀 /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 |
获取当前用户可访问的应用列表 |
| 方法 | 路径 | 说明 |
|---|---|---|
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 类型 | 有效期 | 存储方式 | 说明 |
|---|---|---|---|
| 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}) |
一次性授权码,使用后即失效 |
| Token 类型 | 有效期 | 存储方式 | 说明 |
|---|---|---|---|
| Access Token | 20 分钟 | MySQL + Redis | 子系统本地登录令牌 |
| Refresh Token | 24 小时 | MySQL + Redis | 刷新令牌 |
子系统实现了 JWT Token 的滑动过期机制:
X-New-Token 返回给前端X-New-Token 后替换本地旧 Token// 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}`);
}
}
// 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);
}
);
client_id 和 client_secret[sso] 配置段POST /api/oauth/exchange-code 换码接口/auth/callback,从 URL 提取 code 并调换码接口t_sys_role),确保角色 code 与统一认证平台一致所有接口使用统一的响应格式:
{
"code": "000000",
"message": "success",
"data": { ... },
"timestamp": "2026-05-10T12:00:00Z"
}
| 错误码 | 说明 |
|---|---|
000000 |
成功 |
100001 |
缺少授权码 |
400001 |
SSO 授权码无效 |
400002 |
SSO 用户信息格式异常 |
500001 |
服务器内部错误 |
| 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 |
服务器内部错误 |