澜湄合作平台 · Open API 管理指南
面向平台管理员(A 端)的开放接口管理文档 | 版本 v1.0
1. 概述
Open API 模块允许平台管理员(A 端)为第三方应用(B 端)创建和管理 API 访问凭证,控制权限范围,并监控调用情况。本文档面向 A 端管理员,介绍如何管理开放应用、分配权限、以及使用 SSO 单点登录功能。
核心概念
| 概念 | 说明 |
|---|---|
| OpenApp(开放应用) | 代表一个第三方应用,拥有唯一的 AppId 和 AppSecret |
| Scope(权限范围) | 控制应用可访问哪些 API 接口,如 region:read、sso 等 |
| AppSecret(密钥) | 用于 HMAC-SHA256 签名验证,AES 加密存储,仅创建/重置时展示明文 |
| SSO(单点登录) | A 端用户无感跳转到 B 端系统并自动登录 |
| 请求日志 | 记录每次 API 调用的 URI、方法、IP、响应码、耗时,用于审计 |
接口基础路径
| 域 | 基础路径 | 认证方式 |
|---|---|---|
| 应用管理 | /api/admin/open-apps | Sa-Token 会话登录(admin 域) |
| SSO 跳转(管理后台) | /api/admin/sso | Sa-Token 会话登录(admin 域) |
| SSO 跳转(门户前台) | /api/user/sso | Sa-Token 会话登录(user 域) |
2. 应用管理
2.1 应用列表
| GET | /api/admin/open-apps |
无需额外权限,登录即可访问。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
page | int | 否 | 页码,默认 1 |
pageSize | int | 否 | 每页条数,默认 10 |
keyword | String | 否 | 按应用名称或 AppId 搜索 |
响应示例
{
"code": 200,
"data": {
"records": [
{
"id": 1,
"appId": "lm_a1b2c3d4e5f6",
"appName": "合作伙伴系统",
"status": 1,
"ipWhitelist": "",
"rateLimit": 100,
"expireTime": "2026-12-31 23:59:59",
"remark": "用于对接合作方数据",
"scopes": ["region:read", "city:read", "sso"],
"createTime": "2024-01-15 10:30:00",
"updateTime": "2024-03-20 14:15:00"
}
],
"total": 1
}
}
2.2 创建应用
| POST | /api/admin/open-apps |
权限:system:open-app:create
请求体
{
"appName": "合作伙伴系统", // 必填,应用名称
"status": 1, // 可选,1=启用 0=禁用,默认 1
"ipWhitelist": "192.168.1.0/24", // 可选,IP 白名单,多个用逗号分隔
"rateLimit": 100, // 可选,每分钟请求上限,默认 100
"expireTime": "2026-12-31 23:59:59", // 可选,过期时间,null 表示永不过期
"remark": "用于对接合作方数据", // 可选,备注
"scopes": ["region:read", "city:read", "sso"] // 可选,权限范围列表
}
响应示例
{
"code": 200,
"data": {
"app": {
"id": 1,
"appId": "lm_a1b2c3d4e5f6",
"appName": "合作伙伴系统",
"status": 1,
...
},
"secret": "a8f3b2c1d4e5f6a7b8c9d0e1f2a3b4c5" // ← 明文密钥,仅此一次展示!
}
}
AppSecret 明文仅在创建成功时返回,之后无法再查看。请务必在创建后立即将 AppId 和 Secret 安全地传递给 B 端开发者,并提醒其妥善保管。如果遗失,只能通过「重置密钥」生成新的 Secret(旧 Secret 立即失效)。
2.3 应用详情
| GET | /api/admin/open-apps/{id} |
无需额外权限,登录即可访问。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | Long | 是 | 应用主键 ID(路径参数) |
响应示例
{
"code": 200,
"data": {
"app": {
"id": 1,
"appId": "lm_a1b2c3d4e5f6",
"appName": "合作伙伴系统",
"status": 1,
"ipWhitelist": "",
"rateLimit": 100,
"expireTime": "2026-12-31 23:59:59",
"remark": "用于对接合作方数据",
"scopes": ["region:read", "city:read", "sso"],
"createTime": "2024-01-15 10:30:00",
"updateTime": "2024-03-20 14:15:00"
}
}
}
2.4 更新应用
| PUT | /api/admin/open-apps/{id} |
权限:system:open-app:update
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | Long | 是 | 应用主键 ID(路径参数) |
请求体与创建相同(OpenAppDTO),scope 采用全量覆盖策略(先删后插)。
scopes 数组会完全替换旧的权限范围。如果只想新增一个 scope,需要将原有的 scope 也一并传入。
2.5 重置密钥
| POST | /api/admin/open-apps/{id}/reset-secret |
权限:system:open-app:reset-secret
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | Long | 是 | 应用主键 ID(路径参数) |
响应示例
{
"code": 200,
"data": {
"appId": "lm_a1b2c3d4e5f6",
"secret": "new-a8f3b2c1d4e5f6a7b8c9d0e1f2a3b4c5" // ← 新明文密钥,仅此一次展示!
}
}
2.6 删除应用
| DELETE | /api/admin/open-apps/{id} |
权限:system:open-app:delete
级联删除 Scope 关联记录,并清除 Redis 缓存。删除后 B 端应用的所有 API 调用将立即失败。
2.7 请求日志
| GET | /api/admin/open-apps/{id}/logs |
无需额外权限,登录即可访问。按时间倒序返回。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | Long | 是 | 应用主键 ID(路径参数) |
page | int | 否 | 页码,默认 1 |
pageSize | int | 否 | 每页条数,默认 20 |
响应示例
{
"code": 200,
"data": {
"records": [
{
"id": 1001,
"appId": 1,
"requestUri": "/api/open/v1/regions/countries",
"requestMethod": "GET",
"clientIp": "203.0.113.50",
"responseCode": 200,
"costMs": 45,
"createTime": "2024-06-15 09:30:12"
}
],
"total": 1580
}
}
Scope 选项查询
| GET | /api/admin/open-apps/scopes/options |
返回所有可用的 Scope 列表,用于前端下拉选择。
响应示例
{
"code": 200,
"data": [
{ "scope": "cms:article:read", "label": "CMS文章" },
{ "scope": "region:read", "label": "地区国家" },
{ "scope": "sso", "label": "SSO单点登录" },
...
]
}
3. Scope 权限说明
每个开放应用在创建时需分配 Scope,决定其可访问的 API 范围。以下是系统支持的全部 Scope:
| Scope | 标签 | 允许访问的接口 |
|---|---|---|
cms:article:read | CMS文章 | 文章读取接口 |
cms:page:read | CMS单页 | 单页读取接口 |
cms:category:read | CMS分类 | 分类列表 / 分类详情 |
cms:tag:read | CMS标签 | 标签列表 / 标签详情 |
city:read | 城市数据 | 城市列表 / 城市详情 |
biz:org:read | 商务机构 | 商务机构读取接口 |
biz:resource:read | 商务资源 | 商务资源读取接口 |
media:read | 媒体名录 | 媒体读取接口 |
material:read | 素材库 | 素材读取接口 |
data:report:read | 数据报告 | 数据报告读取接口 |
product:read | 商城商品 | 商品读取接口 |
region:read | 地区国家 | 国家列表 / 子地区列表 |
search:read | 统一搜索 | 搜索接口 |
sso | SSO单点登录 | SSO Token 兑换接口 |
region:read 即可。
4. SSO 单点登录
SSO 允许 A 端(澜湄平台)的已登录用户无缝跳转到 B 端(第三方应用),无需再次登录。
4.1 完整流程
4.2 生成跳转链接
根据当前登录域选择不同的接口:
管理后台(admin 域)
| POST | /api/admin/sso/url |
需 Sa-Token admin 域登录,userType 默认为 admin。
门户前台(user 域)
| POST | /api/user/sso/url |
需 Sa-Token user 域登录,userType 默认为 user。
请求体(两个接口相同)
{
"appId": "lm_a1b2c3d4e5f6", // 必填,B 端应用的 AppId
"userType": "admin", // 可选,用户类型,默认取当前登录域的类型
"targetUrl": "https://b.com/sso/callback" // 可选,覆盖默认跳转地址
}
请求参数说明
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
appId | String | 是 | B 端应用的 AppId,用于关联签名密钥 |
userType | String | 否 | 用户类型,决定返回哪些用户信息:admin = 管理员user = 前台用户(含城市会员、机构会员、媒体会员、个人会员,具体类型通过返回的 memberType 字段区分)管理后台默认 admin,门户前台默认 user |
targetUrl | String | 否 | 跳转目标 URL,不传则使用全局配置 sso.target-url |
响应示例
{
"code": 200,
"data": {
"url": "https://b.com/sso/callback?appId=lm_a1b2c3d4e5f6&code=abc123×tamp=1700000000&nonce=xyz789&sign=..."
}
}
前端调用示例
// 管理后台调用 const res = await fetch('/api/admin/sso/url', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ appId: 'lm_a1b2c3d4e5f6' // userType 可选,管理后台默认 "admin" }) }); // 门户前台调用 const res = await fetch('/api/user/sso/url', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ appId: 'lm_a1b2c3d4e5f6' // userType 可选,门户前台默认 "user" }) }); const { data } = await res.json(); window.open(data.url); // 新窗口跳转到 B 端
4.3 B 端验证签名
跳转 URL 中的签名使用与普通 API 不同的签名内容格式,B 端收到跳转请求后可验证签名以确保来源合法:
签名内容格式(换行符分隔)
appId timestamp nonce SSO ← 固定标识(注意:不是请求方法) code
验证步骤
- 从 URL 参数中提取
appId、timestamp、nonce、code、sign - 验证
timestamp是否在合理范围内(建议 5 分钟内) - 用 AppSecret 按上述格式拼接签名内容,计算 HMAC-SHA256
- 比对计算结果与 URL 中的
sign参数 - 签名验证通过后,用
code调用GET /api/open/v1/sso/token获取用户信息
- SSO 跳转 URL 的签名格式与普通 API 签名格式不同,不能混用
- code 仅可使用一次(Redis GETDEL 原子操作),5 分钟有效期
- B 端必须在服务端完成签名验证和 code 兑换,不要在前端 JavaScript 中操作
不同 userType 返回的字段
| userType | 返回字段 |
|---|---|
admin | userId, username, realName, avatar, email, phone, role("admin") |
user | userId, username, nickname, avatar, email, phone, role("user") 若关联会员:+ memberId, memberNo, memberName, memberType memberType 取值:city=城市会员, org=机构会员, media=媒体会员, personal=个人会员 |
5. 配置项
Open API 模块的相关配置位于 application.yml:
| 配置项 | 默认值 | 说明 |
|---|---|---|
open.api.auth-enabled | false | HMAC 签名验证开关。开发环境建议关闭,生产环境必须开启 |
open.api.encrypt-key | lm-open-api-key! | AppSecret 的 AES 加密密钥,生产环境务必更换 |
open.api.timestamp-skew-seconds | 300 | 时间戳允许偏差(秒) |
open.api.nonce-ttl-seconds | 600 | Nonce 防重放窗口(秒) |
sso.target-url | 空 | SSO 默认跳转目标 URL,可在请求中通过 targetUrl 参数覆盖 |
环境变量覆盖
| 环境变量 | 对应配置项 |
|---|---|
OPEN_API_AUTH_ENABLED | open.api.auth-enabled |
OPEN_API_ENCRYPT_KEY | open.api.encrypt-key |
SSO_TARGET_URL | sso.target-url |
- 将
open.api.auth-enabled设为true - 更换
open.api.encrypt-key为高强度随机密钥 - 配置
sso.target-url为 B 端的实际回调地址
open.api.auth-enabled=false 时:
- Open API 拦截器直接放行,不验证 HMAC 签名、时间戳、Nonce、IP 白名单和速率限制
- 不设置
OpenAppContext(ThreadLocal 为 null),因此 Scope 权限检查也会被跳过 - SSO code 兑换接口(
GET /api/open/v1/sso/token)在 auth-disabled 时跳过 scope 校验,任何 code 均可兑换 - 仅限开发环境使用,生产环境必须开启 auth-enabled
6. 安全注意事项
密钥管理
- AppSecret 以 AES 加密存储,数据库中仅保存密文,无法反查明文
- 明文 Secret 仅在创建和重置时返回一次,平台不保存明文
- 重置密钥后旧密钥立即失效,所有使用旧密钥的请求将返回签名错误
- 传递 Secret 给 B 端时应使用安全渠道,禁止通过邮件、即时通讯工具明文传输
IP 白名单
- 可为应用配置 IP 白名单,仅允许指定 IP 访问
- 支持 CIDR 格式,如
192.168.1.0/24 - 多个 IP 用逗号分隔
- 留空表示不限制
频率限制
- 每个应用独立计数,默认 100 次/分钟
- 超限后返回错误码
2006 - 如 B 端业务需要更高配额,可在应用编辑中调整
rateLimit
过期时间
- 可为应用设置过期时间,到期后所有请求返回错误码
2009 - 留空表示永不过期
- 适合临时合作场景,到期自动失效
SSO 安全
- SSO code 一次性使用,Redis GETDEL 原子操作保证
- code 有效期 5 分钟,过期自动失效
- 跳转 URL 携带 HMAC-SHA256 签名,B 端可验证来源
- 仅拥有
ssoscope 的应用才能兑换 code
7. 常见问题
Q: 创建应用后忘记保存 Secret 怎么办?
Secret 仅在创建时展示一次。如遗失,请使用「重置密钥」功能生成新的 Secret。重置后旧密钥立即失效,请提前通知 B 端开发者。
Q: 如何临时关闭某个应用的访问?
将应用的 status 设为 0(禁用)即可。禁用后所有 API 请求返回错误码 2004,不影响应用配置和 Scope。
Q: 如何查看 B 端的调用情况?
在应用详情中点击「请求日志」,可查看每次调用的 URI、方法、客户端 IP、响应码和耗时。
Q: B 端反馈签名验证失败,如何排查?
1) 确认应用的 status 是否为启用状态
2) 确认应用是否已过期
3) 检查请求日志中的响应码,对照错误码表排查
4) 确认 B 端使用的 Secret 是否为最新(重置后需更新)
Q: SSO 跳转时 targetUrl 不生效?
请求中的 targetUrl 优先级高于全局配置 sso.target-url。如果两者都为空,跳转 URL 会构建失败。请确保至少配置了全局默认地址,或在请求中传入 targetUrl。
Q: 如何为不同 B 端分配不同权限?
为每个 B 端创建独立的 OpenApp,分配所需的 Scope 集合。每个应用有独立的 AppId、Secret 和权限,互不影响。
Q: 应用删除后数据还能恢复吗?
不能。删除操作会级联清理 Scope 关联和 Redis 缓存,不可恢复。如需暂停使用,建议将 status 设为禁用而非删除。