访客标识 Visitor ID¶
Visitor ID 用于在未登录或无法可靠识别用户的场景中,为浏览器生成稳定的匿名标识。
观测云 Browser RUM SDK 可以把该值注入命中规则的 XHR 和 Fetch 请求头,供 API 网关按访客限流;同时将
当前值写入 RUM 事件的 context.visitor_id,用于关联分析。
Visitor ID 只能作为限流分桶和可观测性关联键,不能用于鉴权、租户隔离、计费或审计。
版本要求:RUM SDK 3.3.15 及以上版本。
开启 Visitor ID¶
在 datafluxRum.init() 中配置 visitorId:
datafluxRum.init({
applicationId: "<APPLICATION_ID>",
site: "<PUBLIC_OPENWAY_URL>",
clientToken: "<CLIENT_TOKEN>",
visitorId: {
enabled: true,
header: "x-rum-visitor-id",
match: [
"https://api.example.com/",
/^https:\/\/gateway\.example\.com\//,
function (url) {
return url.includes("/security/")
}
],
ttlHours: 24
}
})
| 参数 | 类型 | 是否必须 | 默认值 | 说明 |
|---|---|---|---|---|
enabled |
Boolean | 否 | false |
是否开启 Visitor ID。未显式设为 true 时不会生成标识或注入请求头。 |
header |
String | 开启时必须 | 无 | 要注入的自定义请求头名称。必须为合法的小写可写请求头,不能使用鉴权、CSRF、签名、链路追踪请求头或 SDK 资源关联头 x-rum-resource-id。 |
match |
Array | 开启时必须 | 无 | 非空 URL 匹配列表,支持 String、RegExp 和 Function。任意一项命中即注入请求头。 |
ttlHours |
Number | 否 | 24 |
Visitor ID 有效时间,单位为小时,必须大于 0。到期后在下一次使用时轮转,不会滑动续期。 |
请求匹配规则¶
match 中不同类型的规则按以下方式匹配:
- String:匹配完整 URL 的前缀,且必须属于同一 Origin(协议、域名、端口)。例如
https://api.example.com不会命中https://api.example.com.evil.test/; - RegExp:针对完整 URL 执行正则匹配;
- Function:接收完整 URL 并返回 Boolean。
匹配函数抛出的异常会被 SDK 隔离,该规则按未命中处理,不会中断业务请求。SDK 自身的 数据上报请求不会被注入 Visitor ID Header。
浏览器 SDK 只处理初始化后发起的 XHR 和 Fetch。页面导航、图片、表单提交、
sendBeacon、WebSocket 和 Worker 内请求不在此能力范围内。
标识生命周期¶
同一浏览器在 Visitor ID 有效期内会复用同一个标识,不会为每个请求生成新值。SDK
优先将标识保存到 localStorage;不可用时依次降级到 sessionStorage 和当前页面内存:
| 存储层级 | 复用范围 |
|---|---|
localStorage |
同一源下的后续页面访问及标签页 |
sessionStorage |
当前标签页会话 |
| 页面内存 | 当前页面生命周期 |
以下情况会生成新值:
- 当前 Visitor ID 超过
ttlHours; - 业务主动调用
resetVisitorId(); - 浏览器存储被清理或不可访问,且原有内存状态已结束。
存在多级有效存储记录时,SDK 会选择最新记录;轮转后不会因刷新恢复旧的降级记录。
本地存储成功时生成持久标识;降级到会话存储或内存时生成会话标识。业务不应解析或 依赖标识格式。
请求头与 RUM 数据¶
请求头只注入命中 match 的 XHR 和 Fetch;开启功能后,当前 Visitor ID 会作为
context.visitor_id 写入 RUM 事件。Visitor ID 与 RUM Session、用户标识和 Trace
相互独立:
sessionSampleRate或tracingSampleRate不决定是否注入 Visitor ID Header;- Visitor ID 不会创建或强制采样 RUM Session;
- 未采样 Session 不会上报 RUM 数据,但命中的业务请求仍可携带 Visitor ID Header;
resetVisitorId()不会修改 RUM Session、用户标识或 Trace。
如果业务已经设置了与 visitorId.header 同名的请求头,SDK 会使用当前 Visitor ID
覆盖该值,并在控制台输出警告。该请求头应只由 SDK 管理;未命中 match 的请求
保留原有请求头,包括 injectTraceHeader 返回的同名头。
由同源 iframe 创建、交给当前页面 fetch() 发送的 Request,会保留原有请求方法、
请求体和业务请求头。同一个 XHR 的 send() 因参数错误同步失败后再次发送时,
不会重复追加 Visitor ID;已经注入该请求的标识不会因重试期间的轮转而改变。
全局、View 和事件自定义上下文中的 visitor_id 不会覆盖 SDK 管理的值。
如需在发送前删除或调整上下文,仍可使用 beforeSend。
初始化前或等待远程配置期间缓冲的自定义事件,在 SDK 启动后会补齐 Visitor ID; 已具有 SDK 标识快照的事件继续使用该快照,不覆盖事件发生时的其他上下文。
如果请求发出后、Resource 完成前发生 Visitor ID 轮转,该 Resource 的
context.visitor_id 仍保留请求头实际使用的旧值;轮转后的新请求和其他后续 RUM
事件使用新值。
主动轮转¶
用户确认退出登录或切换账户后,可以主动轮转 Visitor ID:
调用后,后续新发起的命中请求及后续 RUM 事件使用新值。不要在 401、未认证请求或其他可能 重复触发的失败分支中调用,否则会持续换桶并形成限流绕过通道。
CORS 与安全边界¶
自定义请求头会让跨域请求触发 CORS 预检。API 服务必须在
Access-Control-Allow-Headers 中允许 visitorId.header,并且 match 只应覆盖可信
API。
Visitor ID 由客户端生成,用户可以伪造、替换或删除。网关不能仅凭该值授予权限; 限流策略还应为缺失请求头的请求提供 IP、会话或其他业务维度的兜底规则,并保持较短的 处罚时间,降低攻击者冒用其他访客 ID 造成误封的影响。
Cloudflare 限流示例¶
以下 Cloudflare Advanced Rate Limiting 模板包含两条规则:有 Visitor ID Header 时 按访客标识计数,请求头缺失时按源 IP 兜底。它属于可选的网关部署示例,不是 SDK 运行时依赖;未使用 Cloudflare 的业务可以忽略。
部署前必须修改请求路径、请求头名称、阈值和处罚时间。规则默认关闭,应先创建并读回 检查表达式、计数维度和阈值,确认无误后再启用。
Cloudflare Advanced Rate Limiting 模板
{
"name": "rum_visitor_id_rate_limit",
"description": "Rate limit matched API requests by the RUM Visitor ID header, with an IP fallback when the header is missing.",
"kind": "zone",
"phase": "http_ratelimit",
"rules": [
{
"description": "RUM Visitor ID API rate limit",
"expression": "(starts_with(http.request.uri.path, \"/api/\") and len(http.request.headers[\"x-rum-visitor-id\"]) > 0)",
"action": "block",
"ratelimit": {
"characteristics": [
"cf.colo.id",
"http.request.headers[\"x-rum-visitor-id\"]"
],
"period": 60,
"requests_per_period": 100,
"mitigation_timeout": 600,
"counting_expression": "(starts_with(http.request.uri.path, \"/api/\") and len(http.request.headers[\"x-rum-visitor-id\"]) > 0)",
"requests_to_origin": true
},
"enabled": false
},
{
"description": "Missing RUM Visitor ID IP fallback",
"expression": "(starts_with(http.request.uri.path, \"/api/\") and len(http.request.headers[\"x-rum-visitor-id\"]) eq 0)",
"action": "block",
"ratelimit": {
"characteristics": ["cf.colo.id", "ip.src"],
"period": 60,
"requests_per_period": 30,
"mitigation_timeout": 600,
"counting_expression": "(starts_with(http.request.uri.path, \"/api/\") and len(http.request.headers[\"x-rum-visitor-id\"]) eq 0)",
"requests_to_origin": true
},
"enabled": false
}
]
}
Cloudflare 规则格式可参考通过 API 创建限流规则 和限流参数说明。