澜湄合作平台 · Open API 管理指南

面向平台管理员(A 端)的开放接口管理文档  |  版本 v1.0

1. 概述

Open API 模块允许平台管理员(A 端)为第三方应用(B 端)创建和管理 API 访问凭证,控制权限范围,并监控调用情况。本文档面向 A 端管理员,介绍如何管理开放应用、分配权限、以及使用 SSO 单点登录功能。

核心概念

概念说明
OpenApp(开放应用)代表一个第三方应用,拥有唯一的 AppIdAppSecret
Scope(权限范围)控制应用可访问哪些 API 接口,如 region:readsso
AppSecret(密钥)用于 HMAC-SHA256 签名验证,AES 加密存储,仅创建/重置时展示明文
SSO(单点登录)A 端用户无感跳转到 B 端系统并自动登录
请求日志记录每次 API 调用的 URI、方法、IP、响应码、耗时,用于审计

接口基础路径

基础路径认证方式
应用管理/api/admin/open-appsSa-Token 会话登录(admin 域)
SSO 跳转(管理后台)/api/admin/ssoSa-Token 会话登录(admin 域)
SSO 跳转(门户前台)/api/user/ssoSa-Token 会话登录(user 域)

2. 应用管理

2.1 应用列表

GET/api/admin/open-apps

无需额外权限,登录即可访问。

参数类型必填说明
pageint页码,默认 1
pageSizeint每页条数,默认 10
keywordString按应用名称或 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"  // ← 明文密钥,仅此一次展示!
  }
}
重要:Secret 仅展示一次 AppSecret 明文仅在创建成功时返回,之后无法再查看。请务必在创建后立即将 AppId 和 Secret 安全地传递给 B 端开发者,并提醒其妥善保管。如果遗失,只能通过「重置密钥」生成新的 Secret(旧 Secret 立即失效)。

2.3 应用详情

GET/api/admin/open-apps/{id}

无需额外权限,登录即可访问。

参数类型必填说明
idLong应用主键 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

参数类型必填说明
idLong应用主键 ID(路径参数)

请求体与创建相同(OpenAppDTO),scope 采用全量覆盖策略(先删后插)。

Scope 全量覆盖 更新应用时,传入的 scopes 数组会完全替换旧的权限范围。如果只想新增一个 scope,需要将原有的 scope 也一并传入。

2.5 重置密钥

POST/api/admin/open-apps/{id}/reset-secret

权限:system:open-app:reset-secret

参数类型必填说明
idLong应用主键 ID(路径参数)

响应示例

{
  "code": 200,
  "data": {
    "appId": "lm_a1b2c3d4e5f6",
    "secret": "new-a8f3b2c1d4e5f6a7b8c9d0e1f2a3b4c5"  // ← 新明文密钥,仅此一次展示!
  }
}
重置后旧密钥立即失效 重置密钥后,旧的 AppSecret 会立刻无法使用。请务必提前通知 B 端开发者,并在新密钥生成后及时传递,避免服务中断。

2.6 删除应用

DELETE/api/admin/open-apps/{id}

权限:system:open-app:delete

级联删除 Scope 关联记录,并清除 Redis 缓存。删除后 B 端应用的所有 API 调用将立即失败。

不可恢复 删除操作不可撤销,请确认 B 端已不再使用该应用后再执行删除。

2.7 请求日志

GET/api/admin/open-apps/{id}/logs

无需额外权限,登录即可访问。按时间倒序返回。

参数类型必填说明
idLong应用主键 ID(路径参数)
pageint页码,默认 1
pageSizeint每页条数,默认 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:readCMS文章文章读取接口
cms:page:readCMS单页单页读取接口
cms:category:readCMS分类分类列表 / 分类详情
cms:tag:readCMS标签标签列表 / 标签详情
city:read城市数据城市列表 / 城市详情
biz:org:read商务机构商务机构读取接口
biz:resource:read商务资源商务资源读取接口
media:read媒体名录媒体读取接口
material:read素材库素材读取接口
data:report:read数据报告数据报告读取接口
product:read商城商品商品读取接口
region:read地区国家国家列表 / 子地区列表
search:read统一搜索搜索接口
ssoSSO单点登录SSO Token 兑换接口
最小权限原则 建议仅为应用分配其业务所需的最小 Scope 集合,避免过度授权。如 B 端仅需读取地区数据,只分配 region:read 即可。

4. SSO 单点登录

SSO 允许 A 端(澜湄平台)的已登录用户无缝跳转到 B 端(第三方应用),无需再次登录。

4.1 完整流程

A端(澜湄平台) B端(第三方应用) ───────────── ───────────── │ │ │ 1. 已登录用户点击「进入B端」按钮 │ │ ──────────────────────────────────► │ │ 管理后台: POST /api/admin/sso/url │ │ 门户前台: POST /api/user/sso/url │ │ 后端生成一次性 code (Redis, TTL 300s) │ │ 构建带签名的跳转 URL 返回前端 │ │ │ │ 2. 前端 window.open(url) 跳转 │ │ ──────────────────────────────────► │ │ URL: https://b.com/sso/callback? │ │ appId=lm_xxx&code=xxx │ │ ×tamp=xxx&nonce=xxx&sign=xxx │ │ │ │ 3. B端后端验证签名 │ │ 提取 code │ │ ◄──────────────── │ │ GET /api/open/ │ │ v1/sso/token │ │ ?code=xxx │ │ (带 HMAC Header) │ │ │ │ 4. B端收到用户信息 │ │ 创建/匹配本地用户 │ │ 完成自动登录 │ │ │

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"  // 可选,覆盖默认跳转地址
}

请求参数说明

参数类型必填说明
appIdStringB 端应用的 AppId,用于关联签名密钥
userTypeString用户类型,决定返回哪些用户信息:
admin = 管理员
user = 前台用户(含城市会员、机构会员、媒体会员、个人会员,具体类型通过返回的 memberType 字段区分)
管理后台默认 admin,门户前台默认 user
targetUrlString跳转目标 URL,不传则使用全局配置 sso.target-url

响应示例

{
  "code": 200,
  "data": {
    "url": "https://b.com/sso/callback?appId=lm_a1b2c3d4e5f6&code=abc123&timestamp=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

验证步骤

  1. 从 URL 参数中提取 appIdtimestampnoncecodesign
  2. 验证 timestamp 是否在合理范围内(建议 5 分钟内)
  3. 用 AppSecret 按上述格式拼接签名内容,计算 HMAC-SHA256
  4. 比对计算结果与 URL 中的 sign 参数
  5. 签名验证通过后,用 code 调用 GET /api/open/v1/sso/token 获取用户信息
注意
  • SSO 跳转 URL 的签名格式与普通 API 签名格式不同,不能混用
  • code 仅可使用一次(Redis GETDEL 原子操作),5 分钟有效期
  • B 端必须在服务端完成签名验证和 code 兑换,不要在前端 JavaScript 中操作

不同 userType 返回的字段

userType返回字段
adminuserId, username, realName, avatar, email, phone, role("admin")
useruserId, username, nickname, avatar, email, phone, role("user")
若关联会员:+ memberId, memberNo, memberName, memberType
memberType 取值:city=城市会员, org=机构会员, media=媒体会员, personal=个人会员

5. 配置项

Open API 模块的相关配置位于 application.yml

配置项默认值说明
open.api.auth-enabledfalseHMAC 签名验证开关。开发环境建议关闭,生产环境必须开启
open.api.encrypt-keylm-open-api-key!AppSecret 的 AES 加密密钥,生产环境务必更换
open.api.timestamp-skew-seconds300时间戳允许偏差(秒)
open.api.nonce-ttl-seconds600Nonce 防重放窗口(秒)
sso.target-urlSSO 默认跳转目标 URL,可在请求中通过 targetUrl 参数覆盖

环境变量覆盖

环境变量对应配置项
OPEN_API_AUTH_ENABLEDopen.api.auth-enabled
OPEN_API_ENCRYPT_KEYopen.api.encrypt-key
SSO_TARGET_URLsso.target-url
生产环境必须
  • open.api.auth-enabled 设为 true
  • 更换 open.api.encrypt-key 为高强度随机密钥
  • 配置 sso.target-url 为 B 端的实际回调地址
auth-enabled=false 时的行为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 端可验证来源
  • 仅拥有 sso scope 的应用才能兑换 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 设为禁用而非删除。