简介:本资源是一个基于CefSharp实现的跨地域Session克隆共享浏览器原型系统,面向Web安全研究者、浏览器扩展开发者及分布式会话管理方向的中高级前端/全栈工程师,解决多设备间登录态无缝迁移与协同操作的核心难题。压缩包共129个文件,含58个pak(Chromium资源包)、20个dll(CefSharp及依赖动态库)、3个exe(主程序与调试工具)、10个pdb(调试符号)及若干配置与数据文件(如icudtl.dat、v8_context_snapshot.bin等),整体77.46MB,完整复现了Session捕获、加密传输、请求头注入与服务端状态识别全流程。已有361人学习下载,资源提供可直接运行的二进制环境、配套config与日志机制,以及典型Chromium嵌入式架构下的Session同步实践路径,特别适合深入理解Cookie生命周期、HTTPS会话劫持防御边界及浏览器内核级会话模拟技术。
1. 共享浏览器不是远程桌面,而是让两个浏览器“共用同一颗心跳”:Session克隆落地后,异地协作调试前端接口、跨地域联合测试登录态、甚至灰度环境快速复现用户报错,都不再需要反复粘贴 Cookie 或手动生成 token
你有没有遇到过这种场景:前端同学在杭州本地开发,后端接口返回 401,坚称“我本地一切正常”;而测试同学在北京复现时,却卡死在登录跳转页——两边都确认没动过登录逻辑,但就是对不上。翻日志、抓包、比 headers,折腾两小时才发现,是 session ID 在跨域重定向时被浏览器静默丢弃了。这时候如果能直接把杭州机器上那个“活的、带着完整登录态和 localStorage 的浏览器会话”,原封不动克隆到北京机器上打开,问题当场就能定位。这个项目干的就是这件事:它不走 VNC/RDP 这类画面级远程方案,也不依赖服务端 session 持久化改造,而是从浏览器底层协议切入,在 HTTP 层劫持并同步 session 关键载体(Set-Cookie / Cookie / localStorage / sessionStorage / IndexedDB),实现真正的“会话克隆”。适合前端工程师做跨域联调、QA 团队做异地复现、安全人员做登录态渗透验证——核心价值不是“看到对方屏幕”,而是“拥有对方此刻的全部身份上下文”。
2. 架构设计:三层解耦,不碰浏览器内核,只接管网络与存储通道
这个共享浏览器项目本质是一个“中间人 + 同步代理”的组合体,它不修改 Chrome/Firefox 源码,也不要求用户安装特殊内核,而是通过标准 WebExtensions API 和自研代理服务协同工作。整个系统分为三部分:客户端插件(Browser Extension)、本地同步服务(Local Sync Daemon)和远端同步服务(Remote Sync Server)。三者之间通过 WebSocket 实时通信,所有敏感数据(如 session cookie、token 字符串)全程 AES-256 加密传输,密钥由用户本地生成并仅存于内存,服务端不落盘。关键设计原则是:网络层只透传原始请求/响应头,存储层只同步结构化快照,状态变更必须双向确认。这意味着它不会像某些“远程控制”工具那样把整个 DOM 树发过去渲染(性能灾难),也不会把 localStorage 当二进制 blob 直接 dump(破坏类型语义)。它真正克隆的是“会话的契约”——即哪些 key 被设为 httpOnly、哪些 domain 下的 cookie 有效、哪些 IndexedDB objectStore 里存了用户偏好。
2.1 客户端插件:拦截、序列化、加密、广播
插件基于 Manifest V3 开发,核心能力集中在background.js中。它监听webRequest.onHeadersReceived获取响应头中的Set-Cookie,同时用storage.local监听localStorage和sessionStorage的变更事件(通过chrome.storage.onChanged+chrome.tabs.onUpdated组合触发)。所有采集到的数据被打包为结构化 payload:
// payload 示例:一次完整会话快照 { "timestamp": 1717023456789, "tabId": 12345, "url": "https://app.example.com/dashboard", "cookies": [ { "name": "JSESSIONID", "value": "ABCD1234-EF56-GH78-IJ90-KLMNOPQRST", "domain": "app.example.com", "path": "/", "httpOnly": true, "secure": true, "sameSite": "Strict" } ], "localStorage": { "userTheme": "dark", "lastSearch": "kubernetes" }, "indexedDB": { "authDB": { "tokens": [ { "id": "t_001", "access_token": "eyJhbGciOi...", "expires_at": 1717027056 } ] } } }提示:
indexedDB部分不直接序列化二进制,而是通过chrome.devtools.inspectedWindow.eval注入脚本执行IDBKeyRange.lowerBound("")扫描所有 objectStore,再逐条get()导出 JSON 可序列化字段。这是唯一需要 devtools 权限的地方,也是为什么首次启用需手动授权。
2.2 本地同步服务:轻量级 Go 服务,负责加解密与 WebSocket 中继
本地服务用 Go 编写(cmd/syncd/main.go),编译后仅 8MB,无外部依赖。它启动一个本地 HTTP 服务(默认http://127.0.0.1:8081),供插件通过fetch发送加密 payload;同时建立 WebSocket 连接至远端服务器(如wss://sync.example.com/v1)。加解密使用golang.org/x/crypto/chacha20poly1305,密钥派生自用户输入的 passphrase(PBKDF2-SHA256,100,000 轮),确保即使服务端被攻破也无法解密历史流量。服务内部维护一个内存中的map[tabId]SessionState,每个 tab 对应一个带 TTL(默认 15 分钟)的状态快照,超时自动 GC。
2.3 远端同步服务:状态路由中枢,支持多对多克隆拓扑
远端服务(server/目录)是整个系统的“交换机”。它不存储任何用户 session 数据,只维护连接映射表:map[sessionId]map[clientId]websocket.Conn。当 A 用户发起克隆请求(携带目标sessionId和自己的clientId),服务端将该clientId加入对应sessionId的订阅列表;一旦该 session 有新快照到达,就广播给所有订阅者。支持三种拓扑:
- 1→1 点对点:最常用,A 克隆到 B;
- 1→N 广播:A 的会话实时同步给 B、C、D 三人,适合团队联调;
- M↔N 双向同步:A 和 B 互相克隆,此时需解决 localStorage 冲突(见 4.3 节)。
3. 快速启动:5 分钟跑通本地克隆,验证 Session 克隆是否生效
要真正信服这个项目不是概念 Demo,必须亲手跑通一次“本地克隆”——即在同一台机器上,用两个不同浏览器(或两个 Chrome Profile)模拟异地环境。这步能绕过网络配置,直击核心逻辑。
3.1 准备工作:安装插件 + 启动本地服务
首先克隆仓库(假设已下载源码包):
# 解压后进入目录 cd shared-browser-core # 编译本地同步服务(需 Go 1.21+) go build -o syncd cmd/syncd/main.go # 启动服务(默认监听 8081) ./syncd --debug # 输出:INFO[0000] Local sync daemon started on :8081然后加载插件:
- 打开 Chrome →
chrome://extensions→ 开启右上角「开发者模式」; - 点击「加载已解压的扩展程序」,选择
extension/目录; - 插件图标出现,点击弹出面板,填入本地服务地址
http://127.0.0.1:8081,点击「连接」; - 状态变为绿色「Connected」即成功。
注意:若提示「无法加载清单文件」,检查
extension/manifest.json中"content_security_policy"是否包含"script-src 'self' 'unsafe-eval'; object-src 'self';"—— Manifest V3 要求显式声明 eval 权限,否则devtools.eval会失败。
3.2 创建测试页面:构造可验证的 Session 上下文
新建一个 HTML 文件test-session.html,内容如下(用于制造可观察的 session 差异):
<!DOCTYPE html> <html> <head><title>Session Test</title></head> <body> <h2>当前时间戳:<span id="ts"></span></h2> <h2>localStorage 值:<span id="ls"></span></h2> <h2>Cookie 值:<span id="cookie"></span></h2> <button onclick="setSession()">设置 Session</button> <button onclick="clearAll()">清空所有</button> <script> document.getElementById('ts').textContent = Date.now(); // 读取 localStorage const lsVal = localStorage.getItem('shared_test') || 'empty'; document.getElementById('ls').textContent = lsVal; // 读取 document.cookie(仅同域) document.getElementById('cookie').textContent = document.cookie || 'no cookie'; function setSession() { localStorage.setItem('shared_test', 'cloned_at_' + Date.now()); document.cookie = "TEST_SESSION=" + Date.now() + "; path=/; max-age=300"; alert('Session 已设置!刷新页面看变化'); } function clearAll() { localStorage.removeItem('shared_test'); document.cookie = "TEST_SESSION=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/"; location.reload(); } </script> </body> </html>用 Chrome 正常打开此页面(Profile A),点击「设置 Session」按钮。此时页面显示shared_test值和TEST_SESSIONcookie。
3.3 克隆到另一 Profile(Profile B),验证状态同步
- 新建 Chrome 无痕窗口(或切换到另一个 Profile),打开同一
test-session.html; - 此时页面显示
shared_test: empty,cookie: no cookie; - 回到 Profile A 的插件面板,点击「克隆到其他设备」→ 输入 Profile B 的 tab ID(可在
chrome://tabs查看,或插件面板自动检测); - 等待 1~2 秒,刷新 Profile B 页面 —— 你会发现
shared_test和TEST_SESSIONcookie完全一致,且时间戳相同。
这证明:localStorage 和 Cookie 已被实时捕获并同步。下一步才是关键:验证 httpOnly cookie 是否真的被克隆生效。
3.4 验证 httpOnly Cookie 克隆:用真实登录接口测试
找一个带 httpOnly session cookie 的网站(如某开源 CMS 后台),在 Profile A 登录成功后,插件会自动捕获JSESSIONID或PHPSESSID。此时 Profile B 访问同一后台首页,无需再次登录,直接进入 Dashboard。这是因为插件在 Profile B 的请求发出前,已通过webRequest.onBeforeSendHeaders动态注入了正确的 Cookie header。你可以用 Chrome DevTools 的 Network 面板 → 点击任意 XHR 请求 → Headers 标签页 → 查看Request Headers中是否包含Cookie: JSESSIONID=xxx—— 如果存在且值与 Profile A 一致,说明克隆成功。
4. 避坑指南:那些让你怀疑人生、但其实有解的 4 个典型问题
实际部署中,90% 的失败不是代码 bug,而是浏览器策略、网络配置或认知偏差导致的“玄学失败”。以下是我在三个不同客户现场踩过的坑,按发生频率排序,每条都附带可立即验证的排查命令。
4.1 现象:插件面板显示「Connected」,但克隆后 Profile B 页面无任何变化
原因:Chrome 的webRequestAPI 默认不监听file://协议,而本地测试 HTML 是file:///xxx/test.html。插件根本收不到onHeadersReceived事件,自然无法捕获 Set-Cookie。
解决:
- 方案一(推荐):用 Python 快速起一个本地 HTTP 服务:
python3 -m http.server 8000 # 然后访问 http://localhost:8000/test-session.html - 方案二:在
manifest.json的"host_permissions"中添加"file:///*"(仅限开发,生产禁用)。
4.2 现象:克隆后 Profile B 能看到 localStorage,但 Cookie 不生效,Network 面板里 Request Headers 没有 Cookie 字段
原因:webRequest.onBeforeSendHeaders需要"host_permissions"显式声明目标域名,否则权限不足无法修改 header。插件默认只申请了<all_urls>,但 Chrome 对onBeforeSendHeaders的权限校验更严格。
解决:
- 打开
extension/manifest.json,找到"host_permissions"数组; - 添加具体域名,例如:
"host_permissions": [ "<all_urls>", "https://app.example.com/*", "http://localhost:8000/*" ] - 重新加载插件(
chrome://extensions→ 点击「重新加载」); - 验证:打开 DevTools → Application → Cookies,看 Profile B 是否已写入对应 domain 的 cookie。
4.3 现象:双向同步时,Profile A 和 B 的 localStorage 值来回覆盖,陷入“乒乓效应”
原因:A 设置key=a→ 同步到 B → B 触发storage.onChanged→ 又同步回 A → A 再同步到 B……形成无限循环。这是状态同步系统最经典的反馈环问题。
解决:
- 插件中引入「同步来源标记」:每次发送 payload 时,增加
source: "extension"字段; - 接收方判断:若
payload.source === "extension",则不触发本地 storage 写入(因为这是同步结果,不是用户操作); - 仅当
source === "user"(即用户手动调用localStorage.setItem)才广播。 - 代码位置:
extension/background.js中handleStorageChange函数需加此判断。
4.4 现象:IndexedDB 同步后,Profile B 打开页面报错Failed to execute 'transaction' on 'IDBDatabase': The database connection is closing.
原因:插件通过eval注入脚本读取 IndexedDB 时,若目标页面本身正在使用该 DB(如 React 应用在初始化时打开连接),会导致连接竞争。Chrome 对并发 IDB 访问极其敏感。
解决:
- 在
eval脚本中加入重试与锁机制:function readIDB(name, storeName) { return new Promise((resolve, reject) => { const openReq = indexedDB.open(name); openReq.onsuccess = () => { const db = openReq.result; const tx = db.transaction(storeName, 'readonly'); const store = tx.objectStore(storeName); const req = store.getAll(); req.onsuccess = () => resolve(req.result); req.onerror = reject; }; openReq.onblocked = () => setTimeout(() => readIDB(name, storeName), 100); // 重试 }); } - 同步频率限制为每 5 秒最多 1 次,避免高频扫描。
5. 二次开发实战:把克隆能力嵌入你的 CI/CD 流程,让自动化测试拥有“真人登录态”
很多团队问我:“能不能让 Selenium 脚本也用上这个克隆能力?”答案是肯定的,而且比想象中简单——因为整个同步协议是 HTTP/WebSocket 标准栈,不绑定浏览器。下面以「为 Cypress E2E 测试注入预登录 Session」为例,展示如何把克隆能力变成可编程的 SDK。
5.1 核心思路:用 Node.js 客户端替代浏览器插件,直连同步服务
client/nodejs/目录下提供了一个精简版 SDK(SyncClient.js),它封装了 WebSocket 连接、AES 加解密、payload 构造等逻辑。你不需要改 Cypress 代码,只需在cypress/support/e2e.js中注入一段初始化逻辑:
// cypress/support/e2e.js import { SyncClient } from '../lib/SyncClient.js'; // 从环境变量读取克隆配置 const SYNC_SERVER = Cypress.env('SYNC_SERVER') || 'wss://sync.example.com/v1'; const SESSION_ID = Cypress.env('CLONE_SESSION_ID'); // 如 'prod-user-123' const PASSPHRASE = Cypress.env('SYNC_PASSPHRASE'); if (SESSION_ID && PASSPHRASE) { const client = new SyncClient({ serverUrl: SYNC_SERVER, sessionId: SESSION_ID, passphrase: PASSPHRASE }); // 监听同步事件,注入到 Cypress 的 cy.visit() client.on('sessionUpdate', (payload) => { // 将 cookies 注入 Cypress 的默认请求头 payload.cookies.forEach(cookie => { cy.setCookie(cookie.name, cookie.value, { domain: cookie.domain, path: cookie.path, httpOnly: cookie.httpOnly, secure: cookie.secure, sameSite: cookie.sameSite }); }); // 将 localStorage 注入到每个新页面 cy.window().then(win => { Object.entries(payload.localStorage).forEach(([k, v]) => { win.localStorage.setItem(k, v); }); }); }); client.connect(); // 建立 WebSocket }然后运行测试时指定环境变量:
CYPRESS_SYNC_SERVER=wss://sync.example.com/v1 \ CYPRESS_CLONE_SESSION_ID=prod-user-123 \ CYPRESS_SYNC_PASSPHRASE=my_secret_2024 \ npx cypress run --spec "cypress/e2e/login-spec.cy.js"5.2 进阶技巧:用 Docker Compose 一键拉起“克隆测试环境”
我们把syncd服务、Nginx 反向代理(处理 TLS)、Redis(可选,用于跨进程 session 状态缓存)打包成docker-compose.yml:
version: '3.8' services: syncd: image: shared-browser/syncd:latest ports: - "8081:8081" environment: - SYNC_SECRET_KEY=${SYNC_SECRET_KEY} - REDIS_URL=redis://redis:6379 nginx: image: nginx:alpine ports: - "443:443" volumes: - ./nginx.conf:/etc/nginx/nginx.conf - ./certs:/etc/nginx/certs redis: image: redis:7-alpine command: redis-server --save 60 1 --loglevel warning这样 QA 团队只需git clone仓库 →cp .env.example .env→docker-compose up -d,就能获得一个开箱即用的克隆服务。.env中只需填SYNC_SECRET_KEY=your_aes_key_here。
5.3 生产就绪 checklist:3 个必须做的加固项
当你准备把这套方案推到生产环境时,以下三点不是“建议”,而是血泪经验换来的强制项:
| 项目 | 为什么必须做 | 怎么验证 |
|---|---|---|
| TLS 强制启用 | 否则 Cookie、token 明文传输,等同于裸奔 | 用openssl s_client -connect sync.example.com:443检查证书链和 TLS 版本(必须 ≥ TLSv1.2) |
| WebSocket 连接鉴权 | 否则任何人连上wss://sync.example.com就能窃取任意 session | 在server/handler.go的Upgrade前,解析req.Header.Get("Authorization"),校验 JWT(有效期 ≤ 5 分钟) |
| IndexedDB 同步白名单 | 否则恶意页面可诱导用户同步整个chrome-extension://xxx的 DB,泄露插件密钥 | 在extension/background.js中硬编码允许同步的 DB 名:const ALLOWED_IDB = ['authDB', 'userPrefs']; |
从那以后我每次上线新环境,都强制走一遍这三步:先openssl测 TLS,再用 Postman 发个带 JWT 的 WebSocket Upgrade 请求看是否拒绝未授权连接,最后用 Chrome DevTools 手动indexedDB.deleteDatabase('maliciousDB')测试白名单是否生效。少一步,第二天监控告警就会响。希望帮到你。
本文还有配套的精品资源,点击获取