远端业务系统接入平台时,容易出问题的往往不是接口调用本身,而是“谁持有凭据、凭据多久失效、成员被撤销后多久生效”。这三件事决定了系统能不能长期稳定运行。下面是我们在这条链路上守住的做法和边界。

凭据分两级,长期那一级不出服务端

平台侧有两类身份。模块服务身份是长期凭据,只在业务系统服务端持有,用来交换 Launch Grant(POST /integration/v1/sso/launch-grants/exchange)和申请实例服务 Token(POST /integration/v1/service-tokens);实例服务 Token 则是最长五分钟、Audience 固定、Scope 已按本次请求收窄的短期凭据,响应带 no-store

因此浏览器永远拿不到模块凭据:前端只把用户带来的 Launch Code 交给自己的后端,其余交换都在服务端完成。SDK 对短期 Token 的处理是按“实例 + 本次请求的 Scope 集合”缓存,最长 300 秒、没有 Refresh,剩余寿命不足安全窗口时重新申请,并发调用只触发一次,且不跨实例复用。

Launch 是一次性交换,换来的是本地会话

用户从平台进入业务系统时,带的是 Launch Code,不是长期凭据。后端用模块服务身份交换并消费 Grant,随后建立业务系统自己的本地会话;会话里只放派生身份,不放平台 Token、Launch Code、Client Secret 或完整 Claims。

交换与会话校验都要验签:用平台运行时 JWKS 校验签名算法、Issuer、Audience、有效期、token_use、Account 与 AppInstance 以及 Scope,公钥来源禁止重定向并限制响应体大小。这里有一个容易混淆的地方:登录说明页上的“联机正常”只证明后端能解析平台公钥,不代表用户已登录,也不代表 Launch 链路可用——三件事要分开描述。

成员被撤销后,本地会话要能收敛

平台会话有效,不等于本地会话可以一直用下去。长期会话期间,成员被移出实例、角色发生变化或实例被停用,都应在有限时间内反映到业务系统。做法是用实例服务身份批量校验(POST /integration/v1/session-checks):active 为真要求实例、实例成员、Account 与 Account 成员同时有效;非成员与不存在的用户同样返回 active=false,不泄漏成员是否存在。业务系统按自己声明的校验窗口收敛,实践中写请求的窗口可以比读请求更短。

Scope 不够就在本地失败,不要发出请求

每个运行时请求都带 Scope。SDK 只在平台实际授予的 Scope 覆盖本次请求时才返回可用凭据,否则在本地失败,不发业务请求。这条规则的意义在于把“平台没授权”和“平台调用失败”分开:前者是配置问题,应当立刻可见,而不是变成一串难以定位的上游错误。

幂等与重试归调用方

SDK 不自动重试任何请求。写操作必须由调用方提供稳定的幂等键,请求失败也不重发——因为“结果未知”和“失败”是两种状态,只有业务系统自己知道该补发、查询还是提示用户。错误方面,平台的 Problem Details 会映射到稳定的错误类别(未认证、无权限、冲突、限流、服务端错误等)并标注是否可重试;凭据与响应正文不会进入日志或错误序列化。

两份制品,两条获取路径

两组 SDK 的可获取性不同,接入前先确认自己属于哪一类。TypeScript / Node 侧是公共 npm 上的 @td-runlume/platform-integration-sdk,Apache-2.0,用于服务端调用。Java 侧坐标是 app.runlume.platform:platform-integration-sdk-java,由平台的 Nexus 制品库提供、不在 Maven Central,且只能凭平台授予的凭据与许可使用,不要重新分发制品;Java 侧通常配合 Spring Boot Starter 使用,SDK 组件与本地会话有效性管线由 Starter 装配,业务系统只提供会话边界与工作区读取两个扩展点。

接口路径、Scope 名称与可用能力以签约时的契约和平台文档为准。本文只说明接入时服务端要守住的边界;平台提供的能力范围可以从平台能力开始了解。