OpenChamber UI 认证模块深度解析:密码会话、WebAuthn 通行密钥与可信设备凭据体系
发布时间:2026/9/25 4:06:21来源:尧图网络
AI Agent人工智能代码智能体交互助手【免费下载链接】openchamberAgentic Development Environment based on OpenCode AI agent项目地址https://gitcode.com/gh_mirrors/op/openchamber点击查看免费下载导读本文以 OpenChamber 服务端 UI 认证模块packages/web/server/lib/ui-auth/及其配套的lib/client-auth/为对象系统讲解浏览器端访问控制的三层能力密码会话认证password session auth、WebAuthn 通行密钥passkeys与可信设备trusted-device会话。读者读完本文将掌握 OpenChamber 会话 Cookie 的 host:port 作用域设计、JWT 会话令牌的签发与校验流程、登录限流参数、通行密钥的注册/认证协议细节以及远程客户端 Bearer Tokenoc_client_...与 Pairing v2 配对流程的完整实现链路。模块定位单一凭据模型三种签发方式该模块的架构核心是一条清晰的原则可信设备访问只有一种持久化凭据——由packages/web/server/lib/client-auth/remote-clients.js存储的远程客户端 Bearer Token。密码password、WebAuthn 通行密钥passkey和 Pairing v2 都只是这种凭据的签发方式而不是彼此独立的凭据系统。签发方式 凭据形态 ───────────────────────────── ────────────────────────────── UI 密码登录 ───────────▶ oc_client_xxx Bearer Token 浏览器通行密钥登录 ───────────▶ oc_client_xxx Bearer Token Pairing v2 配对 ───────────▶ oc_client_xxx Bearer Token签发后的客户端 Token 只会原样返回一次服务端仅保存其 SHA-256 哈希见 remote-clients.js 中hashToken与tokenHash字段后续请求通过Authorization: Bearer oc_client_...完成认证。Pairing v2 由 pairing.js 实现它维护短期有效的一次性配对会话仅存哈希后的 secret在/api/client-auth/pairing/*下暴露 create / cancel / redeem 路由redeem 一个有效配对 secret 后即签发与密码/通行密钥完全相同的远程客户端 Token。模块入口与文件结构认证能力分布在五个文件中职责边界清晰文件职责ui-auth.jsUI 认证控制器运行时Cookie/会话签发、JWT 校验、登录限流、全部认证路由处理器ui-passkeys.js通行密钥存储与 WebAuthn 注册/认证验证辅助逻辑session-cookie.js依据请求Host端口解析会话 Cookie 名被会话签发/校验与通知身份提取共用remote-clients.js可信设备客户端 Token 存储、Bearer 认证、最近使用时间跟踪与吊销pairing.js短期 Pairing v2 会话与一次性 secret 赎回为可信设备 Token在服务装配层index.js 将上述运行时实例化并注入路由系统createRemoteClientAuthRuntime使用数据目录下的remote-clients.jsoncreateClientPairingRuntime使用client-pairing-sessions.json数据目录默认为~/.config/openchamber可用OPENCHAMBER_DATA_DIR覆盖见 index.js。会话 Cookie 作用域按 host:port 隔离问题背景issue #2377浏览器按 RFC 6265 的规定Cookie 存储仅以host 为键、不区分端口。因此同一主机名、不同端口上运行的两个 OpenChamber 实例会共享同一个oc_ui_sessionCookie——在第二个实例登录会覆盖第一个实例的会话 Cookie。该问题对 LAN 地址和本机回环loopback地址同样成立例如localhost:3000与localhost:3001就无法安全共存。解决方案把端口折叠进 Cookie 名sessionCookieNameForRequest(req, base)session-cookie.js把请求Host中的端口折进 Cookie 名Host 携带显式端口包括来自x-forwarded-host的端口时返回oc_ui_session_portHost 不带显式端口时返回裸名oc_ui_session。实现细节值得注意session-cookie.jsforwarded 优先优先取x-forwarded-host的第一个值按逗号分隔并 trim其次才回退到host头IPv6 支持正确处理[::1]:3000与[::1]两种形态的 authority 解析非法端口兜底端口非纯数字如192.168.0.1:notaport或数值非正数时返回裸 base 名。同一解析器被四处共享密码登录、浏览器通行密钥登录的issueSession签发 Cookie以及会话校验、会话清除、通知身份提取request-security.js 使用同一sessionCookieNameForRequest解析身份。注意身份提取路径不提供 CSRF Token而 Bearer Token 校验路径保持不变。测试用例佐证session-cookie.test.js 用七组用例固化了上述语义可当作行为规格阅读无显式端口的主机192.168.0.1、example.com、空串、undefined均返回裸名oc_ui_session同一 IP 不同端口:3000、:3001、:8080分别得到oc_ui_session_3000/3001/8080实现实例隔离IPv6 authority[::1]:3000返回oc_ui_session_3000[::1]返回裸名x-forwarded-host端口优先于直接 host127.0.0.1:3902x-forwarded-host: 203.0.113.9:8443→oc_ui_session_8443非法端口回退裸名自定义 base 名my_app同样参与端口折叠。升级兼容性升级行为上本次改动会为一切显式端口 host重命名 Cookie因此已登录的浏览器会话需要重新登录一次磁盘上的数据格式无任何变化无 on-disk 迁移。密码会话认证ui-auth.js控制器创建createUiAuth({ password, cookieName, sessionTtlMs, readSettingsFromDiskMigrated })创建 UI 认证控制器。当password未配置时控制器进入 disabled 分支enabled: false各路由返回 400/401 占位响应配置密码后进入完整认证逻辑enabled: true见 ui-auth.js。会话令牌HS256 JWT会话 Cookie 的值是一个 JWTui-auth.jsconst token await new SignJWT({ type: ui-session }) .setProtectedHeader({ alg: HS256 }) .setIssuedAt() .setExpirationTime(ttlMs / 1000 s) .sign(jwtSecret);签名密钥从OPENCODE_JWT_SECRET环境变量或数据目录jwt-secret文件读取不存在时用crypto.randomBytes(32)生成并以0o600权限持久化ui-auth.js。注意handleResetAuth全局登出在设置了OPENCODE_JWT_SECRET时不可用会抛出 400——因为无法轮换环境变量来源的密钥。TTL 默认值普通会话 12 小时SESSION_TTL_MS勾选“信任此设备”后 7 天TRUSTED_DEVICE_SESSION_TTL_MS见 ui-auth.js。校验isSessionValid使用 jose 的jwtVerify并延迟加载import(jose)首次会话检查才发生避免随服务器启动拖慢冷启动见 ui-auth.js。Cookie 属性无论签发还是清除会话 Cookie 均带固定属性ui-auth.jsPath/; HttpOnly; SameSiteStrict; Max-Age秒; ExpiresUTC # 当请求为 HTTPSreq.secure 或 x-forwarded-proto 为 https时追加 SecureSameSiteStrictHttpOnly的组合有效压制 CSRF 与 XSS 窃取 Cookie 的风险面。密码校验密码在createUiAuth时以crypto.scryptSync(password, salt, 64)生成期望哈希每次登录使用crypto.timingSafeEqual常数时间比较ui-auth.js同时normalizePassword会先做 Unicode 归一化normalize()与 trim。登录限流与防爆破handleSessionCreate在验证密码前先执行内存级滑动窗口限流ui-auth.js相关常量参数默认值说明OPENCHAMBER_RATE_LIMIT_MAX_ATTEMPTS10每窗口每 IP 最大失败次数OPENCHAMBER_RATE_LIMIT_NO_IP_MAX_ATTEMPTS3无法解析客户端 IP 时的更严格上限RATE_LIMIT_WINDOW_MS5 分钟计数窗口RATE_LIMIT_LOCKOUT_MS15 分钟超限后的锁定时长RATE_LIMIT_CLEANUP_MS1 小时过期/陈旧记录的清理周期响应头携带X-RateLimit-Limit / X-RateLimit-Remaining / X-RateLimit-Reset被限流时返回 429 与Retry-After。IP 解析优先x-forwarded-for首值并剥离::ffff:IPv4-mapped 前缀其次req.ip/remoteAddress限流键基于 IP无 IP 时统一落到rate-limit:no-ip。同一键的计数与锁定通过内存 Promise 队列串行化acquireRateLimitLock并通过unref()的定时器做周期清理ui-auth.js。WebAuthn 通行密钥ui-passkeys.js运行时创建createUiPasskeys({ passwordBinding, readSettingsFromDiskMigrated, storeFile, rpName, challengeTtlMs })创建通行密钥运行时。关键前提是Passkey 依赖 UI 密码保护已启用assertEnabled()在passwordBinding为空时抛出 400“Passkeys require UI password protection to be enabled”。存储与绑定存储文件数据目录下ui-passkeys.json默认~/.config/openchamber/ui-passkeys.jsonJSON 结构含version当前 1、userIDbase64url、passwordBinding、passkeys[]ui-passkeys.js。密码绑定passwordBinding是HMAC-SHA256(jwtSecret, normalizedPassword)的十六进制摘要ui-auth.js。加载存储时若passwordBinding不匹配会直接清空全部 passkey 并重写存储——这意味着修改 UI 密码会使既有通行密钥失效这是刻意设计的绑定语义ui-passkeys.js。用户 ID首次创建时crypto.randomBytes(32)生成作为 WebAuthn 的userID与userName: openchamber-ui、userDisplayName: OpenChamber UI一起传给注册选项。注册流程beginRegistration / finishRegistrationbeginRegistration(req, { label })先解析当前请求的 rpID 与 origin优先x-forwarded-proto/x-forwarded-host其次 socket/host 头再用simplewebauthn/server的generateRegistrationOptions生成选项要求residentKey: required、userVerification: required、attestationType: none并以excludeCredentials排除当前 rpID 下已注册凭据生成的 challenge 连同expectedOrigins含当前 origin 与磁盘设置里的publicOrigin、expectedRPIDs、TTL默认 5 分钟存入内存registrationChallenges返回{ requestId, optionsJSON }finishRegistration(payload)校验请求 ID 与 challenge 存活verifyRegistrationResponse要求requireUserVerification: true验证通过后将credential.publicKey、counter、transports、deviceType、backedUp等持久化ui-passkeys.js。认证流程beginAuthentication / finishAuthentication认证选项只allowCredentials列出当前 rpID 下已注册的凭据finishAuthentication用存储的公钥与counter调用verifyAuthenticationResponse成功后回写新的counter与lastUsedAtui-passkeys.js。认证完成后handlePasskeyAuthenticationVerify会签发会话 Cookie且与密码登录一致地支持trustDevice与issueClientToken参数ui-auth.js。按 host 隔离与吊销getStatus/listPasskeys都按当前请求解析出的 rpID 过滤实现同一实例不同 host 的通行密钥互相不可见isLocalRpId覆盖localhost、127.0.0.1、::1revokePasskey(req, id)只删除与当前 rpID 匹配的凭据找不到返回 404clearAllPasskeys()清空全部并轮换 userID配合handleResetAuth的rotateJwtSecret() 清除会话 Cookie实现“全部设备强制下线”。可信设备凭据远程客户端 Tokenremote-clients.jscreateRemoteClientAuthRuntime({ fsPromises, path, crypto, storePath })管理oc_client_...令牌生命周期签发generateToken()oc_client_前缀 32 字节 base64url 随机串服务端只存sha256(token)的 hex 哈希明文仅返回一次remote-clients.js。Bearer 认证authenticateBearerToken(token, req)要求 Token 带oc_client_前缀用constantTimeEqual常数时间比对哈希并检查expiresAt是否过期同时记录lastUsedAt写入节流 60 秒与lastTransport——通过请求头x-openchamber-relay-connection判定请求经由 relay 隧道还是直连remote-clients.js。去重语义createClient支持dedupeKey——同 key 再次签发会替换旧记录且标签优先级为“显式 label 被替换记录的 label 客户端自报 fallback”保证重配对不丢失人工命名remote-clients.js。吊销与清理revokeClient(id)标记revokedAtpurgeRevokedClients()物理删除hasActiveRelayClients()基于usesRelay或实测lastTransport relay判断 relay 需求驱动中继生命周期决策。客户端元数据clientKind、authMethod、pairingId、deviceName/Platform/Model、appVersion都会归一化持久化供设备列表展示。Pairing v2一次性配对会话pairing.jscreateClientPairingRuntime({ ..., storePath, remoteClientAuthRuntime, ttlMs })实现配对流程默认 TTL 10 分钟创建createPairingSession生成pair_前缀的会话 ID、32 字节 base64url secret、XXXX-XXXX格式 4 字节指纹secret 只存 SHA-256 哈希支持allowedClientKinds合法值mobile/desktop默认两者皆可、createdByClientId、usesRelaypairing.js。取消cancelPairingSession(id)置cancelledAt会话即不可再 redeem。赎回redeemPairingSession按“未取消、未使用、未过期、clientKind 在允许集合内、secret 常数时间比对通过”五重校验失败统一返回Invalid or expired pairing session通用错误不泄露存在性。成功后调用remoteClientAuthRuntime.createClientdedupeKey默认pairing:sessionIdauthMethod: pairing并把会话标记为已使用、记录产出的clientIdpairing.js。存储写入client-pairing-sessions.json目录0o700、文件0o600withStoreMutation队列串行化全部变更过期会话由sweepExpiredSessions/sweepExpiredSessionsFromStore清理。配对链接由此成为“短生命周期、一次性、可取消”的受控入口与 UI 登录页的二维码/链接配对体验对应。附加能力URL 认证令牌oc_url_源码中还存在一类短时 URL 令牌oc_url_ 24 字节随机串TTL 仅 60 秒ui-auth.js。通过POST /auth/url-token携带scope参数签发随后在 URL 查询参数oc_url_token或/api/fs/serve/页面子资源的 Referer 中携带scope 收窄guest:id作用域令牌只允许GET /api/guests/id/...路径防止 iframe 内客脚本越权读取其他包文件无 scope仅允许可读 GET 路径如/api/event、/api/fs/raw、/api/fs/serve、/api/notifications/stream等与白名单 WebSocket 路径如/api/event/ws、/api/terminal/ws、/api/dictation/ws、/api/dev-tunnel响应始终带Cache-Control: no-store。公共 API 一览createUiAuth 返回ui-auth.jsenabled、requireAuth(req,res,next)、requireSessionAuth(req,res,next)、resolveAuthContext(req,res,opts)、handleSessionStatus、handleSessionCreate、handleUrlAuthToken、handlePasskeyStatus、handlePasskeyRegistrationOptions、handlePasskeyRegistrationVerify、handlePasskeyAuthenticationOptions、handlePasskeyAuthenticationVerify、handlePasskeyList、handlePasskeyRevoke、handleResetAuth、ensureSessionToken、dispose()。createUiPasskeys 返回ui-passkeys.jsenabled、getStatus(req)、listPasskeys(req)、revokePasskey(req, passkeyId)、clearAllPasskeys()、beginRegistration(req, { label })、finishRegistration(payload)、beginAuthentication(req)、finishAuthentication(payload)、dispose()、isLocalRpId。认证优先级与使用建议从requireAuth与resolveAuthContext的实现可以推断出请求的认证判定顺序会话 CookieJWT→ URL 令牌oc_url_→ Bearer 客户端令牌oc_client_任一通过即放行全部失败时清除 Cookie 并返回 401JSON API 返回{ error: UI authentication required, locked: true }非 API 请求返回纯文本Authentication required。handleSessionStatus对携带 Bearer 头的探测请求只按令牌本身作答避免 Cookie 与已吊销令牌互相掩盖ui-auth.js。实践要点总结多实例同 host 部署依赖自动的oc_ui_session_port隔离无需手工改 Cookie 名升级后让用户重新登录一次即可修改 UI 密码会同时作废全部通行密钥passwordBinding 不匹配即清空存储可视为一次主动的凭据收敛可信设备凭据是唯一的持久授权密码/通行密钥/配对只是签发途径运维侧吊销设备应操作remote-clients.json对应记录的 revokePairing 会话与配对令牌均为一次性secret 仅存哈希、redeem 即失效且支持取消与指纹展示适合作为移动端/桌面端首次连接的安全通道。赞分享AI Agent人工智能代码智能体交互助手【免费下载链接】openchamberAgentic Development Environment based on OpenCode AI agent项目地址https://gitcode.com/gh_mirrors/op/openchamber点击查看免费下载相关推荐LKY Office Tools从0到可用只需3步免费的Office一键部署LKY Office Tools从0到可用只需3步免费的Office一键部署 刚装完 Windows想在 10 分钟内用上 Word 和 Excel却要后端API网关MCP 服务dsh-pluginPocket ID通行密钥原理WebAuthn与FIDO2技术深度解析Pocket ID通行密钥原理WebAuthn与FIDO2技术深度解析 Pocket ID是一个简单易用的OIDC身份提供商专门支持通行密钥认证技术让用户后端认证鉴权Meteor Accounts 完全指南用户模型、会话存储与密码/OAuth 登录认证体系深度解析Meteor Accounts 完全指南用户模型、会话存储与密码/OAuth 登录认证体系深度解析 Meteor 的 Accounts 系统是在 userId后端前端开发工具移动开发上一篇Xous网络与通信服务从DNS到USB设备的完整通信栈下一篇iOS富文本设计模式基于TTTAttributedLabel的架构设计创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网