扣子Coze实现ChatSDK的会话隔离(纯前端,萌新必看)
   ·  
 项目背景
使用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,保存生成的公钥和私钥,私钥文件用记事本打开即可
 完整代码
js代码:
// 加载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);
    }
})();
把测试网页也贴一下,创建一个txt文件,把代码复制进去,再重命名为index.html,双击打开就可以了。
<!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 class="container">
        <div class="header">
            <h1>Coze 智能体测试页面</h1>
        </div>
    </div>
    <script>
        //在此插入上述代码
    </script>
</body>
</html>
成功的话应该会看到这样的界面,并且实现了用户对话隔离:
 安全问题
由于直接把公钥和私钥写在了js里,肯定有安全问题,但毕竟是全靠前端实现,不知道有啥解决方案( •̀ ω •́ )✧。
更多推荐
 


所有评论(0)