项目背景
使用 Coze 提供的代码在网页插入智能体后,发现不同用户之间没有实现会话隔离(可以互相看到对话记录)。
虽然官方文档里也给了解决方案,但写得比较粗略,对低代码用户非常不友好,而且示例代码给的是 Python 的,似乎需要部署后端才能实现。
本文介绍一种纯前端实现用户隔离的方案。
实现原理
先来看官方提供的代码:
<script src="https://lf-cdn.coze.cn/obj/unpkg/flow-platform/chat-app-sdk/1.2.0-beta.10/libs/cn/index.js"></script>
<script>
new CozeWebSDK.WebChatClient({
config: {
bot_id: '**********', // 智能体 ID
},
componentProps: {
title: 'Coze',
},
auth: {
type: 'token',
token: 'pat_********', // 访问令牌
onRefreshToken: function () {
return 'pat_********'; // 备用访问令牌,可以不填
}
}
});
</script>
由于 Coze 是根据令牌来区分对话记录,而我们将令牌写死在代码里,导致所有用户使用的是同一个令牌,看到的也是相同的对话记录。
也就是说,只要给不同用户发不同的令牌,就可以实现用户隔离。
我们可以使用 Coze 提供的 OAuth 应用来为每个用户自动申请新令牌,完整流程如下。

前期准备
需要准备以下四个要素:
- 智能体 ID
- OAuth ID
- OAuth 公钥
- OAuth 私钥
智能体
创建并发布智能体,智能体 ID 可以在地址栏里找到。

OAuth
创建 OAuth 应用,类型为服务类应用,保存应用 ID。

权限勾选 "Bot 管理"、"会话管理"、"文件"、"消息"。

点击创建 Key,保存生成的公钥和私钥,私钥文件用记事本打开即可。

完整代码
JavaScript 代码:
// 加载 JWT 生成库并初始化 Coze 智能体,替换你的配置后,加到网页的 js 里
(function() {
// 配置参数(替换为你的实际信息)
const COZE_CONFIG = {
botId: '1145141145141', // 智能体 ID
appId: '*************', // OAuth ID
publicKey: '******************************************************', // OAuth 公钥
privateKey: `-----BEGIN PRIVATE KEY----- ****************************************************** ****************************************************** -----END PRIVATE KEY-----` // OAuth 私钥,全复制进来
};
// 加载 JWT 库
function loadJwtLibrary() {
return new Promise((resolve, reject) => {
// 检查是否加载成功
if (window.KJUR && window.KJUR.jws && window.KJUR.jws.JWS) {
resolve();
return;
}
const script = document.createElement('script');
script.src = 'https://cdnjs.cloudflare.com/ajax/libs/jsrsasign/8.0.20/jsrsasign-all-min.js';
script.type = 'text/javascript';
script.crossOrigin = 'anonymous';
// 处理跨域问题
// 加载成功回调
script.onload = () => {
// 验证库是否正确加载
if (window.KJUR && window.KJUR.jws && window.KJUR.jws.JWS) {
resolve();
} else {
reject(new Error('JWT 库加载但未正确初始化'));
}
};
// 加载失败回调
script.onerror = () => {
reject(new Error(`JWT 库加载失败,请检查链接:${script.src}`));
};
document.head.appendChild(script);
});
}
// 生成符合 Coze 要求的 JWT
function generateCozeJwt(userUid) {
const header = {
alg: 'RS256',
typ: 'JWT',
kid: COZE_CONFIG.publicKey
};
const currentTime = Math.floor(Date.now() / 1000);
const payload = {
iss: COZE_CONFIG.appId,
aud: "api.coze.cn",
jti: Math.random().toString(36).substr(2, 32) + Date.now(),
iat: currentTime,
exp: currentTime + 3600,
session_name: userUid
};
const formattedPublicKey = COZE_CONFIG.privateKey;
return window.KJUR.jws.JWS.sign(header.alg, JSON.stringify(header), JSON.stringify(payload), formattedPublicKey);
}
// 获得 Token 并创建 Coze 智能体界面
async function getAccessToken(jwt) {
try {
const response = await fetch("https://api.coze.cn/api/permission/oauth2/token", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${jwt}`
},
body: JSON.stringify({
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
duration_seconds: 900
})
});
if (!response.ok) {
throw new Error(`获取 token 失败,HTTP 状态码:${response.status}`);
}
const data = await response.json();
new CozeWebSDK.WebChatClient({
config: { bot_id: COZE_CONFIG.botId },
componentProps: { title: 'Coze AI 助手' },
auth: {
type: 'token',
token: data.access_token,
onRefreshToken: () => data.access_token
}
});
} catch (error) {
throw new Error(`获取 Access Token 失败:${error.message}`);
}
}
// 获取用户唯一标识
function getUserUid() {
// 从本地存储获取登录用户 ID
const loggedUserId = localStorage.getItem('website_user_id');
if (loggedUserId) return loggedUserId;
// 生成临时访客 ID
let visitorId = localStorage.getItem('coze_visitor_id');
if (!visitorId) {
visitorId = `visitor_${Date.now()}_${Math.random().toString(36).slice(-6)}`;
localStorage.setItem('coze_visitor_id', visitorId);
}
return visitorId;
}
// 初始化 Coze 聊天
function initCozeChat() {
if (!window.CozeWebSDK || !window.CozeWebSDK.WebChatClient) {
console.error('Coze SDK 未加载');
return;
}
const userUid = getUserUid();
const jwtToken = generateCozeJwt(userUid);
getAccessToken(jwtToken);
}
// 加载 Coze SDK
function loadCozeSdk() {
const script = document.createElement('script');
script.src = "https://lf-cdn.coze.cn/obj/unpkg/flow-platform/chat-app-sdk/1.2.0-beta.10/libs/cn/index.js";
script.onload = initCozeChat;
script.onerror = () => console.error(`Coze SDK 加载失败:${COZE_CONFIG.sdkSrc}`);
document.head.appendChild(script);
}
// 初始化流程
async function init() {
try {
await loadJwtLibrary();
loadCozeSdk();
} catch (error) {
console.error('初始化失败:', error.message);
}
}
// 页面加载完成后初始化
if (document.readyState === 'complete') {
init();
} else {
window.addEventListener('load', init);
}
})();
测试网页示例:创建一个 HTML 文件,将上述代码插入 <script> 标签中。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Coze 智能体测试页面</title>
</head>
<body>
<div>
<div>
<h1>Coze 智能体测试页面</h1>
</div>
</div>
<script>
// 在此插入上述代码
</script>
</body>
</html>
成功加载后应显示如下界面,并且实现了用户对话隔离:

安全提示
由于直接把公钥和私钥写在了 JS 里,存在一定安全风险。建议在生产环境中结合后端代理进行密钥管理,仅在前端演示或低风险场景下采用此方案。
