Review and prepare third-party SSO onboarding for Miduo Planet using exported shortcut Markdown handoff files. Use when the user mentions third-party access, shortcut export MD, appCode/appSecret, validate-login-token, refresh-session-token, revoke-session-token, login bridge, or asks whether a third-party system can correctly integrate Miduo login.
This skill is a copy-paste-ready onboarding/review blueprint for integrating any new third-party project with Miduo Planet SSO.
It supports both workflows:
Use this skill when:
LOCAL_SSO_9186_DEMO-77 (1).mdIncluded sample handoff:
LOCAL_SSO_9186_DEMO-77 (1).md is sample data for local testing. It contains example appSecret and localhost URLs and must not be reused verbatim in production. Always treat secrets as sensitive.Always read these in order:
miduo-md/todo/令牌发放-todo/快捷入口-令牌发放与第三方校验-技术分析.mdmiduo-md/todo/令牌发放-todo/快捷入口-令牌发放与第三方校验-curl测试文档.mdmiduo-md/todo/令牌发放-todo/快捷入口-令牌发放与第三方校验-本机9186测试结果.mdIf the export Markdown conflicts with tested behavior, trust tested behavior.
Do both:
Miduo SSO uses two tokens:
token): short-lived, opaque, one-time token delivered to the browser via redirect (QUERY or FORM_POST).Non-negotiables:
appSecret is backend-only; never expose to browsers/mobile apps.mobile, name, etc.) as identity proof.redirectUri must match allowlist (strict). Production should require HTTPS. (Current backend code accepts http/https, but HTTPS is strongly recommended.)state must be generated by the third party and verified on callback (CSRF/session binding).validate-login-token, Miduo returns wework_userid when valid=true. This value is the authoritative Enterprise WeChat userid (企业微信用户ID).Use this as the first checklist in both review/build mode. Do not start coding until P0 items are confirmed.
对接环境
MIDUO_BASE_URL:<https://admin.t.ebcone.cn>(第三方后端调用 open API / 登录桥的域名)<dev/test/prod>,时区(默认 Asia/Shanghai)应用凭据(仅后端)
appCode:<...>appSecret:<...>(只能放第三方后端,禁止出现在前端/浏览器)第三方回调/落地(必须提供完整 URL)
callbackUrl(你方接收 exchange token 的入口页面/接口对应的页面):<https://third.example.com/sso/callback>QUERY 或 FORM_POST(默认 QUERY;若 FORM_POST,第三方必须实现自动提交表单接收)登录桥回跳白名单(必须精确列出)
redirectUri allowlist(至少包含 callbackUrl;推荐 HTTPS):
<https://third.example.com/sso/callback>state 策略(防 CSRF)
state 如何生成、保存(cookie/session/db)、回调时如何校验与过期(建议 5–10 分钟)第三方必须具备的后端接口/能力
POST /auth/miduo/callback(或等价):前端把 token/state 交给后端validate-login-token / refresh-session-token / revoke-session-tokensessionToken 只存后端(db/redis),不会写入浏览器存储X-App-Timestamp 偏差 ±<300> 秒<600> 秒(第三方侧需要保证同 appCode 下 nonce 不复用)【第三方系统接入确认单】
1. 环境
- 对接环境: <dev/test/prod>
- MIDUO_BASE_URL: <https://...>
2. 应用凭据(第三方后端持有)
- appCode: <...>
- appSecret: <已通过安全渠道接收/已配置在密钥管理,不出现在前端>
3. 回调与白名单
- callbackUrl: <https://.../sso/callback>
- redirectUri allowlist:
- <https://.../sso/callback>
- tokenDeliveryMode: <QUERY|FORM_POST>
4. state 与会话
- state 生成方式: <...>
- state 保存位置: <cookie/session/db>
- state 过期时间: <...>
5. 令牌与存储
- exchange token 仅用于一次校验: <确认>
- sessionToken 仅后端存储: <确认>
- refresh 策略: <定时/按请求/混合,频率...>
- logout revoke: <确认>
Treat these as required unless the user explicitly says the audience only needs a subset.
The handoff should clearly contain:
baseUrlappCodeappSecretshortcutIdcallbackUrl(第三方落地页/回调入口)redirectUri allowlist(登录桥回跳白名单)QUERY or FORM_POSTuserId, mobile, ...)
allowedClaims controls optional PII fields like mobile/email. When returned, these fields are plain/original (not masked/encrypted), so treat them as sensitive.Recommended (if missing, flag as P1):
The handoff should clearly contain:
POST /api/sso/redirect-url(发放跳转 URL)GET /api/gzt/workbench/shortcut/jump/{shortcutId}(工作台快捷入口统一跳转)POST /api/open/sso/validate-login-token(校验 exchange token)POST /api/open/sso/refresh-session-token(续期 sessionToken)POST /api/open/sso/revoke-session-token(吊销 sessionToken)GET /api/sso/bridge/redirect-url(登录桥:失效后回米多再回第三方)Open API headers (required):
Content-Type: application/json
X-App-Code: <appCode>
X-App-Timestamp: <unix seconds>
X-App-Nonce: <random string>
X-App-Signature: <HMAC-SHA256 signature>
Canonical string:
canonical = appCode + "\n" + timestamp + "\n" + nonce + "\n" + signedValue
signature = HMAC_SHA256(appSecret, canonical) // Base64 output recommended
Reference implementations (copy-paste safe):
// Node.js
import crypto from "crypto";
export function miduoSignatureBase64({ appCode, appSecret, timestamp, nonce, signedValue }) {
const canonical = `${appCode}\n${timestamp}\n${nonce}\n${signedValue}`;
return crypto.createHmac("sha256", appSecret).update(canonical, "utf8").digest("base64");
}
// Java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public static String miduoSignatureBase64(String appCode, String appSecret, String timestamp, String nonce, String signedValue) throws Exception {
String canonical = appCode + "\n" + timestamp + "\n" + nonce + "\n" + signedValue;
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(appSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
byte[] raw = mac.doFinal(canonical.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(raw);
}
Signed value per endpoint:
signedValue = tokensignedValue = sessionTokensignedValue = sessionTokenThe handoff should clearly explain:
exchange token; browser must not hold appSecretexchange tokenexchange token is one-time and should fail on second validatesessionToken server-sidesessionTokensessionTokensuccess=false), go through login bridgeredirectUri must be HTTPS and allowlistedQUERY: landing receives query paramsFORM_POST: landing receives form bodyCopy-paste this section to the third-party team. Replace all <...> placeholders. Do not send real appSecret in group chats; use secure channels.
环境: <DEV|TEST|UAT|PROD>
米多星球 Base URL: <https://admin.xxx.com> # 第三方对接 open API/登录桥的域名
米多星球 前端登录页: <https://admin.xxx.com/login> # 浏览器重认证会跳这里
时区: Asia/Shanghai
签名时间戳单位: unix seconds
允许时钟偏差: ±<300> seconds
nonce 去重窗口: <600> seconds
appCode: <YOUR_APP_CODE>
appName: <YOUR_APP_NAME>
appSecret: <YOUR_APP_SECRET> # 仅第三方后端保存与使用
appStatus: <1=启用>
callbackUrl(落地/回调): <https://third.example.com/sso/callback>
redirectUri allowlist(登录桥白名单):
- <https://third.example.com/sso/callback> # 推荐精确匹配;至少需同域同host命中
allowedClaims(可返回字段):
- userId
- employeeNo
- userName
- mobile
- email
exchangeTokenTTLSeconds: <600>
sessionTokenTTLSeconds: <28800>
sessionMaxAgeSeconds: <null|604800>
tokenDeliveryMode: <QUERY|FORM_POST> # 默认 QUERY
登录桥入口(浏览器 GET):
GET {MIDUO_BASE_URL}/api/sso/bridge/redirect-url
?appCode=<appCode>
&redirectUri=<url-encoded redirectUri>
&state=<state>
登录桥回跳(第三方 callback 接收):
<redirectUri>?token=<exchangeToken>&app_code=<appCode>&state=<state>&source=bridge
注意:
- `state` 必须由第三方生成并在 callback 时校验(CSRF/会话绑定)
- `redirectUri` 必须命中 allowlist;生产强烈建议使用 https
- `redirectUri` 可能在多层重定向后仍是 percent-encoded,一般只需 decode 一次(避免双重 decode)
Required headers:
Content-Type: application/json
X-App-Code: <appCode>
X-App-Timestamp: <unix seconds>
X-App-Nonce: <random string>
X-App-Signature: <HMAC-SHA256(appSecret, canonical) base64>
Canonical string:
canonical = appCode + "\n" + timestamp + "\n" + nonce + "\n" + signedValue
signature = Base64(HMAC_SHA256(appSecret, canonical))
Endpoint list:
POST {MIDUO_BASE_URL}/api/open/sso/validate-login-token
body: { "token": "<exchangeToken>" }
signedValue = token
POST {MIDUO_BASE_URL}/api/open/sso/refresh-session-token
body: { "sessionToken": "<sessionToken>" }
signedValue = sessionToken
POST {MIDUO_BASE_URL}/api/open/sso/revoke-session-token
body: { "sessionToken": "<sessionToken>" }
signedValue = sessionToken
Response rules(务必按业务字段判断成功):