AIP TALK 第三方 SSO / 嵌入集成指南

将 AIP TALK 对话能力嵌入到第三方门户、ERP 或办公平台。本指南面向第三方系统开发、运维人员,提供从配置到运行的完整步骤与可直接复制粘贴的代码示例。

SSO iframe JWT postMessage

当前服务器地址

能力概览

第三方系统可以通过标准 SSO 流程,让用户无感登录 CowAgent,并在自己的页面内嵌入 AIP TALK 智能对话窗口。集成后可实现:

  • 单点登录:第三方用户通过 JWT assertion 换取 CowAgent token,无需再次输入密码。
  • iframe 嵌入:一行 iframe 即可将 AIP TALK 嵌入到第三方页面。
  • 主题/语言控制:父页面可通过 postMessage 设置主题与语言。
  • 历史与决策:可直接调用 AIP-TALK 历史、收藏、决策标记与复盘 API。

前置条件与配置清单

在 CowAgent 服务端完成以下配置后,第三方系统才能接入。

配置项 说明
multi_user_mode 必须设置为 true,SSO 流程才能启用。
auth_secret CowAgent v2 token 签名密钥,必须设置为强随机字符串。
web_public_url 建议配置公网地址,用于生成文档中的示例 URL。
sso_trusted_issuers 添加第三方系统的 issuer 配置,见下方示例。
其中 allowed_origins 可在登录 CowAgent 后,进入 系统管理 > 第三方集成设置 在线修改,保存后立即生效,无需重启服务。

config.json 中的 issuer 示例

{
  "multi_user_mode": true,
  "auth_secret": "your-strong-random-secret",
  "sso_trusted_issuers": [
    {
      "issuer_id": "partner_erp",
      "secret": "issuer-shared-secret-only-you-and-cowagent-know",
      "audience": "http://localhost:9899",
      "tenant_mapping": {},
      "default_role_ids": ["tenant:user"],
      "auto_create_tenant": true,
      "auto_create_user": true,
      "sync_attributes": true,
      "assertion_ttl_seconds": 60,
      "ip_allowlist": ["10.0.0.0/8"],
      "enabled": true,
      "default_return_to": "/chat#view=aip-talk",
      "embed_return_to": "/embed/aip-talk",
      "allowed_origins": ["https://partner.example.com"],
      "embed_title": "AIP TALK",
      "embed_subtitle": "AI-Powered Automation for Every Decision",
      "welcome_greeting": "我是企业AI大脑,请提问"
    }
  ]
}

embed_title / embed_subtitle / welcome_greeting 为可选项, 分别控制嵌入页头部两行标题与欢迎屏初始提示语;不配置时使用上方缺省值。

注意:issuer 的 secret 必须严格保密,只应存在于 CowAgent 服务端和第三方服务端,禁止暴露在前端代码中。

快速开始:三步完成嵌入

1

配置 issuer

在 CowAgent 的 config.json 中添加上方 issuer 示例。 allowed_origins 与页面文案(embed_title / embed_subtitle / welcome_greeting) 可在应用内「系统管理 > 第三方集成设置」中在线修改,无需重启。

2

换取 token

第三方后端生成 JWT assertion,调用 POST /api/v1/sso/token 获取 CowAgent v2 token。

3

嵌入 iframe

在第三方页面使用 iframe 加载 /embed/aip-talk?token=<TOKEN>,并确保域名已加入 allowed_origins(留空则不限制来源)。

参考实现下载

我们提供了一套可直接运行的最小示例工程 3rd-apitalk-int,包含 Vite 前端、Python Flask 后端代理、SSO assertion 签名、iframe 嵌入、主题/语言控制以及 Playwright/pytest 测试。你可以将其作为第三方门户接入 AIP-TALK 的起点。

下载 3rd-apitalk-int.zip 已排除 node_modules / venv / 日志,约 25 KB

目录结构

3rd-apitalk-int/
├── server/
│   ├── app.py          # Flask 后端:保管 issuer secret、签发 assertion、换 token
│   ├── config.py       # 配置读取
│   └── sso.py          # SSO JWT assertion 生成
├── src/
│   ├── main.ts         # 页面入口与表单控制
│   ├── embed.ts        # AipTalkEmbed:iframe 创建、postMessage 协议
│   ├── api.ts          # 与后端代理通信
│   └── styles.css
├── tests/e2e/          # Playwright 端到端测试
├── tests_py/           # pytest 后端/链路测试
├── .env.example
├── package.json
├── requirements.txt
└── README.md

快速运行

# 1. 启动 CowAgent(默认 http://localhost:9899)

# 2. 启动示例后端代理
cd 3rd-apitalk-int
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
python server/app.py

# 3. 启动前端
cd 3rd-apitalk-int
npm install
npm run dev

# 4. 浏览器打开 http://localhost:6001,填写 CowAgent 服务地址并加载 AIP-TALK
提示:示例工程默认复用 CowAgent 中已存在的 demo_erp issuer, allowed_origins 需包含 http://localhost:6001。生产环境请替换为自有 issuer 配置。
推荐:在 系统管理 → 第三方集成设置 中,点击发行方卡片上的 「生成示例工程」可直接下载已按该发行方真实配置(issuer_id / secret / 服务地址 / 前端 origin)预填好的 ZIP, 生成时还会自动把服务地址与前端 origin 同步进 additional_audiences / allowed_origins; 点击 「查看示例代码」可复制带真实配置的 Python / Node.js / 前端 iframe / cURL 片段。 下面的通用 ZIP 使用 demo_erp 占位配置,需手动对齐后才能访问成功。

SSO 断言格式

assertion 是一个标准的 HMAC-SHA256(HS256) JWT。CowAgent 会校验签名、有效期、受众、issuer 身份,并防止同一 jti 被重放。

JWT Header

{
  "alg": "HS256",
  "typ": "JWT"
}

JWT Payload 字段

字段必填说明
iss是issuer_id,必须与 config.json 中配置的 issuer_id 一致。
sub是第三方用户唯一标识。
tid是第三方租户/组织唯一标识。
aud条件若 issuer 配置了 audience,则必须匹配。
iat / exp是签发与过期时间,过期时间距签发时间不能超过 assertion_ttl_seconds。
jti是唯一 nonce,用于防止重放攻击。
name否用户显示名称,auto_create_user 时会同步。
email否用户邮箱。
roles否角色列表,未提供时使用 issuer.default_role_ids。

Python 生成 assertion

import base64
import hashlib
import hmac
import json
import time
import uuid

ISSUER_SECRET = "issuer-shared-secret-only-you-and-cowagent-know"
AUDIENCE = "http://localhost:9899"

def b64url(data: bytes) -> str:
    return base64.urlsafe_b64encode(data).decode().rstrip("=")

def make_assertion(issuer_id: str, user_id: str, tenant_id: str) -> str:
    header = {"alg": "HS256", "typ": "JWT"}
    now = int(time.time())
    payload = {
        "iss": issuer_id,
        "sub": user_id,
        "tid": tenant_id,
        "name": "张三",
        "email": "zhangsan@example.com",
        "aud": AUDIENCE,
        "iat": now,
        "exp": now + 60,
        "jti": uuid.uuid4().hex,
        "roles": ["tenant:user"],
    }
    header_b64 = b64url(json.dumps(header, separators=(",", ":")).encode())
    payload_b64 = b64url(json.dumps(payload, separators=(",", ":")).encode())
    signing_input = f"{header_b64}.{payload_b64}".encode()
    signature = b64url(hmac.new(ISSUER_SECRET.encode(), signing_input, hashlib.sha256).digest())
    return f"{header_b64}.{payload_b64}.{signature}"

assertion = make_assertion("partner_erp", "user-001", "org-001")
print(assertion)

后端换 Token

第三方服务端生成 assertion 后,调用 CowAgent 的 token 交换接口,获取可在前端使用的 v2 token。

cURL

curl -X POST http://localhost:9899/api/v1/sso/token \
  -H "Content-Type: application/json" \
  -d '{"assertion":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."}'

Python

import requests

resp = requests.post(
    "http://localhost:9899/api/v1/sso/token",
    json={"assertion": assertion},
)
data = resp.json()
print(data["token"])        # CowAgent v2 token
print(data["user_id"])      # 自动生成的用户 ID
print(data["tenant_id"])    # 自动生成的租户 ID
print(data["expires_in"])   # 秒

成功响应示例

{
  "status": "success",
  "token": "cowagent-v2-token-string",
  "user_id": "ext:partner_erp:user-001",
  "tenant_id": "ext:partner_erp:org-001",
  "roles": ["tenant:user"],
  "expires_in": 2592000
}

浏览器跳转模式

如果第三方系统希望使用 Cookie 会话而不是前端 Bearer token,可以让浏览器自动提交表单到 /auth/sso。登录成功后 CowAgent 设置 cow_auth_token cookie 并 302 跳转。

<form id="sso-form" method="POST" action="http://localhost:9899/auth/sso">
  <input type="hidden" name="assertion" value="GENERATED_ASSERTION" />
  <input type="hidden" name="return_to" value="/embed/aip-talk" />
</form>
<script>document.getElementById("sso-form").submit();</script>

该模式适合同域或一级域名相同的场景;跨域 iframe 嵌入建议使用后端换 token 方案。

iframe 嵌入

拿到 token 后,在第三方页面渲染 iframe。CowAgent 会校验 embedding origin 是否在 allowed_origins 中,并通过 Content-Security-Policy: frame-ancestors 限制 framing。allowed_origins 留空时不限制来源,任何 origin 均可嵌入(响应头为 frame-ancestors *)。

最小嵌入代码

<iframe
  id="aip-talk-frame"
  src="http://localhost:9899/embed/aip-talk?token=YOUR_TOKEN"
  width="100%"
  height="600"
  frameborder="0"
  allow=" microphone"
  sandbox="allow-scripts allow-same-origin allow-forms"
></iframe>

动态高度建议

window.addEventListener("message", (event) => {
  if (event.origin !== "http://localhost:9899") return;
  if (event.data?.type === "cow:resize" && event.data.height) {
    document.getElementById("aip-talk-frame").style.height = event.data.height + "px";
  }
});

父页面 postMessage 协议

嵌入页加载完成后会向父页面发送 cow:ready。父页面可发送以下消息控制嵌入页。

消息类型方向说明
cow:ready子 → 父嵌入页初始化完成,event.data 包含 version。
cow:set-theme父 → 子设置主题,payload: {"theme": "light|dark"}。
cow:set-lang父 → 子设置语言,payload: {"lang": "zh|en"}。
cow:refresh-token父 → 子刷新 token,payload: {"token": "NEW_TOKEN"}。

完整示例

const iframe = document.getElementById("aip-talk-frame");
const allowedOrigin = "http://localhost:9899";

window.addEventListener("message", (event) => {
  if (event.origin !== allowedOrigin) return;

  if (event.data?.type === "cow:ready") {
    console.log("AIP TALK ready, version:", event.data.version);

    // 同步第三方系统的主题
    const theme = localStorage.getItem("theme") || "light";
    iframe.contentWindow.postMessage({ type: "cow:set-theme", theme }, allowedOrigin);

    // 同步语言
    iframe.contentWindow.postMessage({ type: "cow:set-lang", lang: "zh" }, allowedOrigin);
  }
});

// token 即将过期时刷新
function refreshToken(newToken) {
  iframe.contentWindow.postMessage({ type: "cow:refresh-token", token: newToken }, allowedOrigin);
}

直接调用 AIP-TALK API

除了 iframe,第三方系统也可以直接调用后端 API。所有 API 都需要在请求头中携带 Authorization: Bearer <token>。

获取会话历史

curl -G http://localhost:9899/api/v1/aip-talk/history \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -d "session_id=session_xxx" \
  -d "is_decision=true"

标记关键决策

curl -X POST http://localhost:9899/api/v1/aip-talk/messages/session_xxx/42/decision \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -d '{
    "is_decision": true,
    "title": "Q3 销售目标调整",
    "note": "基于上月数据,建议将 Q3 目标上调 10%",
    "expected_outcome": "季度销售额达到 1000 万"
  }'

列出决策复盘

curl -G http://localhost:9899/api/v1/insights/decisions \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -d "session_id=session_xxx" \
  -d "status=reviewed" \
  -d "page=1" \
  -d "page_size=20"
完整接口规范见 /api/aip-talk/openapi.json,可导入 Postman 或 Swagger UI 使用。

安全最佳实践

  • issuer secret 只存服务端:禁止将 secret 写入前端 JS、Git 仓库或日志。
  • 断言短有效期:建议 exp - iat ≤ 60 秒,并同步配置 assertion_ttl_seconds。
  • 唯一 jti:每次换 token 必须生成新的 UUID,避免重放攻击。
  • HTTPS only:生产环境必须使用 HTTPS,防止 assertion 和 token 被中间人截获。
  • 严格 allowed_origins:只添加真正需要嵌入的域名,避免被未知站点 iframe 嵌套。留空表示不限制来源(任何站点均可嵌入),生产环境建议显式配置。
  • token 过期刷新:在 token 过期前通过后端重新换取,并通过 cow:refresh-token 同步给 iframe。

故障排查

现象 排查方向
401 / token invalid token 是否已过期;请求头是否正确携带 Authorization: Bearer ...。
403 Origin not allowed 检查 issuer allowed_origins 是否包含当前页面的协议+域名+端口;注意 http 与 https 被视为不同 origin。allowed_origins 留空时任何 origin 均可嵌入,不会再返回 403。
iframe 被 CSP 拒绝 浏览器控制台查看 Refused to frame,确认响应头 Content-Security-Policy: frame-ancestors 包含当前 origin。
assertion expired 确认服务器时间与 CowAgent 服务器时间同步;缩短 exp - iat 或增大 assertion_ttl_seconds。
unknown issuer 确认 iss 与 config.json 中 issuer_id 完全一致(区分大小写)。
assertion already used 同一个 jti 已被使用过,必须每次生成新的 jti。

AIP TALK 第三方集成指南 · CowAgent Enterprise AI

OpenAPI: /api/aip-talk/openapi.json