身份校验
借助一个由您的后端掌控的签名,识别已登录的用户,并跨设备恢复其对话历史。
为什么要校验#
默认情况下 SDK 是匿名的:它会生成一个稳定的 visitor_id 并保存在平台密钥库中。这足以支撑单台设备上的单个对话。 但要识别某个具体用户、在重新登录或换设备时恢复历史, 并可靠地把对话绑定到某个联系人档案,后端就需要证据证明客户端确实是它所声称的那个人。 如果 SDK 只是简单地发送「我是用户 42」,任何人都可以伪造成别的 id 去读取他人的聊天—— 所以 Respondo 要求提供一个只有您的后端才能生成的加密签名 userHash。
获取 identity secret#
identity_secret 是一个绑定到您的 agent(您的挂件渠道所连接的 AI 工作单元)的密钥字符串。 请在控制台中生成它:Channels → Widget → Identity verification。 由于它保存在 agent 上,因此以该 agent 为后端的所有渠道——网页挂件和移动 SDK ——共用同一个密钥。它赋予了签署身份的权限, 因此必须只保存在您的后端,绝不能随应用一起分发。
userHash 公式#
userHash 是对单个被签署的身份字符串做 HMAC-SHA256,并编码为小写十六进制:
userHash = HMAC_SHA256( identity_secret, payload )
payload = userId // 若设置了 userId
= email // 否则,若设置了 email
= (invalid) // 若两者都为空,则没有可签署的内容- 密钥是 HMAC 的密钥(key);身份字符串是消息(message)—— 不能反过来。
- 只签署一个字符串——原始的
userId(或 email), 不加盐、也不套 JSON 外壳。 - 如果您同时传入
userId和 email, 请签署userId(它优先)。 - 输出是十六进制(SHA-256 为 64 个字符),不是 base64。
后端示例#
在您的后端计算签名,并把算好的 userHash 交给应用。
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
)
// HMAC_SHA256(identity_secret, userId),输出为小写十六进制。
func ComputeUserHash(identitySecret, userID string) string {
mac := hmac.New(sha256.New, []byte(identitySecret))
mac.Write([]byte(userID))
return hex.EncodeToString(mac.Sum(nil))
}const crypto = require("crypto");
// 返回 userHash,用于在移动端传入 Respondo.identify。
function computeUserHash(identitySecret, userId) {
return crypto
.createHmac("sha256", identitySecret)
.update(userId, "utf8")
.digest("hex");
}import hmac
import hashlib
# 返回 userHash,用于在移动端传入 Respondo.identify。
def compute_user_hash(identity_secret: str, user_id: str) -> str:
return hmac.new(
identity_secret.encode(),
user_id.encode(),
hashlib.sha256,
).hexdigest()<?php
// 返回 userHash,用于在移动端传入 Respondo.identify。
function computeUserHash(string $identitySecret, string $userId): string {
return hash_hmac('sha256', $userId, $identitySecret);
}哈希如何抵达 Respondo#
/identity 这样的路由可供调用,也不需要注册任何东西。 userHash 只是一个字段,它随 SDK 本来就会发出的请求一起传输。一共两跳,而只有第一跳需要您来实现:
- 您的后端 → 您的应用。怎么把哈希送过去由您决定。最省事的做法是在您本来就会返回的登录/初始化响应里 多加一个字段——不需要额外的往返请求。在您自己的后端上开一个专门的接口(比如
POST /myapp/identity返回{ userId, userHash })同样可行。Respondo 不提供这个接口——它由您来实现。 - 您的应用 → Respondo。这一跳由 SDK 代劳。只要您调用了
identify, SDK 就会把哈希附加到每个相关请求上,后端每次都会重新校验它。
POST /api/v1/chat body identity.userHash
GET /api/v1/chat/resume query user_hash
GET /api/v1/chat/history query user_hash
GET /api/v1/chat/ws frames user_hash (subscribe/identify JSON frames after connect — not in the handshake URL)
GET /api/v1/widget/tours query user_hash
GET /api/v1/widget/checklists query user_hash
POST /api/v1/widget/push/register body user_hash哈希在每个请求上都会被重新校验,而不是一次换取一个会话——正是这一点让偷来的 userId 单靠自己毫无用处。
identify 永远不会被调用,聊天就会以匿名方式运行。这是预期行为,不是 Respondo 的错误——您自己路径上的 404 是您这边的待办事项,而不是集成坏了。向 SDK 传入身份#
从您的后端获取已签署的身份,然后在任意平台把 userHash 传入 identify:
Respondo.identify(
RespondoIdentity(userId = "42", email = "user@example.com", userHash = hash),
)Respondo.identify(
RespondoIdentity(userId: "42", email: "user@example.com", userHash: hash)
)Respondo.identify(RespondoIdentity(
userId: '42', email: 'user@example.com', userHash: hash,
));userHash 无效时的行为#
当 agent 上启用了校验而签名缺失或错误时,后端会执行静默降级为匿名: 聊天仍然可用, visitor_id 会保留,对话会绑定到匿名联系人——但不会关联到用户档案,也没有跨设备历史。 如果某个用户「无法被识别」,几乎总是签名的问题:请检查您签署的是 userId(不是 email 也不是 JSON)、 用的是正确的 identity_secret、 并且输出的是小写十六进制。如果 agent 的 identity_secret 为空,则校验关闭, userId / email 会被原样接受。
轮换 identity_secret 实际上是一次硬切换:用旧密钥生成的每个哈希都会立即失效, 因此已登录的用户会静默回退为匿名,直到您的后端用新密钥重新计算并重新提供他们的 userHash。 只有当您能在同一时间窗口内更新签名端时,才去轮换该密钥。