ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Cookie状态管理与油猴脚本实现持久登录

Cookie状态管理与油猴脚本实现持久登录 1. 这不是“万能登录器”而是对现代网站认证机制的一次务实解剖你有没有试过早上打开 GitHub刚敲完密码弹出邮箱验证下午再点进去又得重输晚上想提交代码验证码刷得比呼吸还快。不是你手速慢是网站在用一套你根本没机会参与设计的规则反复确认“你真的是你”。而所谓“全自动登录”从来不是靠什么黑科技密钥而是理解这套规则后在它允许的边界内把重复动作压缩到一次——甚至零次。我做自动化脚本开发七年从早期用 Selenium 模拟点击到后来研究 OAuth 流程再到如今专注 Cookie 生命周期管理踩过的坑足够填满三个 GitHub 仓库。最深的体会是所有“免验证码”“持久登录”的实现本质都是对 HTTP 协议中状态管理机制的精准复用而不是绕过它。标题里提到的“Cookie 格式”“油猴脚本”“GitHub 邮箱验证”不是孤立工具或技巧而是一条完整链路的三个关键切口Cookie 是状态载体油猴是执行环境邮箱验证是触发条件。它们共同指向一个被很多人忽略的事实——网站并不拒绝自动登录它只拒绝“不合规的自动登录”。关键词里“抖音来客的 cookie 持久化登录”最近热度很高但背后逻辑和 GitHub 完全一致都是基于 SameSite、HttpOnly、Secure 等字段的组合策略只是抖音来客的 Cookie 过期时间更短、校验更频繁。这说明什么说明平台在升级风控但底层协议没变。我们真正要学的不是某个网站的“破解口诀”而是读懂浏览器发给服务器的那串看似杂乱的Set-Cookie头看懂油猴脚本里document.cookie和GM_xmlhttpRequest的分工边界搞清为什么 GitHub 的邮箱验证页面会返回302跳转而非直接渲染表单。这些才是能迁移到任何网站的硬功夫。这篇文章不教你“一键登录所有网站”的幻术而是带你亲手拆解一个真实案例从 GitHub 登录页开始抓包分析完整认证链路手动构造合法 Cookie用油猴脚本注入并维持会话最后处理邮箱验证这个高频卡点。过程中你会看到为什么有些 Cookie 写进去立刻失效为什么油猴脚本在 GitHub 页面上读不到document.cookie的全部内容为什么“持久登录”在技术上必然存在时间上限所有答案都藏在浏览器开发者工具 Network 标签页里那几行不起眼的响应头中。2. Cookie 不是字符串而是一组带约束条件的状态声明很多人以为 Cookie 就是user_idabc123; tokenxyz789这样一串键值对复制粘贴就能用。这是最大的误解。Cookie 实际上是服务器通过Set-Cookie响应头下发的一组带元数据的状态声明每一条都包含至少七个关键字段缺一不可。GitHub 的登录成功响应头里你大概率会看到类似这样的三行Set-Cookie: _gh_sesseyJfcmFpbHMiOnsiZm9vIjoiYmFyIn0sImV4cCI6MTc1MzQ1NjAwMH0%3D--a1b2c3d4e5f6; Path/; HttpOnly; Secure; SameSiteLax Set-Cookie: logged_inyes; Path/; Domaingithub.com; ExpiresThu, 01 Jan 2037 00:00:00 GMT; HttpOnly; Secure; SameSiteLax Set-Cookie: _octoGH1.1.987654321.1753456000; Path/; Domaingithub.com; ExpiresThu, 01 Jan 2037 00:00:00 GMT; HttpOnly; Secure; SameSiteLax这三行不是并列关系而是分层协作的会话凭证体系。我们逐行拆解其真实含义2.1_gh_sess加密会话令牌核心身份凭证Path/表示该 Cookie 在整个 github.com 域名下所有路径都有效HttpOnly是关键它禁止 JavaScript 通过document.cookie读取此 Cookie这是 GitHub 防 XSS 攻击的核心防线Secure强制要求只能通过 HTTPS 传输明文 HTTP 下浏览器直接忽略SameSiteLax限制跨站请求时的发送时机比如从其他网站跳转过来时不会携带防止 CSRF值eyJfcmFpbHMiOnsiZm9vIjoiYmFyIn0sImV4cCI6MTc1MzQ1NjAwMH0%3D--a1b2c3d4e5f6是 JWT 格式前半段 Base64 解码后为{_rails:bar,exp:1753456000}exp字段明确标出过期时间戳2025年7月25日。提示油猴脚本无法读取_gh_sess因为HttpOnly属性由浏览器强制执行。试图用document.cookie获取它只会得到空字符串。这是设计使然不是脚本写错了。2.2logged_inyes用户态标识前端可读的“开关”Domaingithub.com明确指定作用域避免子域名污染Expires设定绝对过期时间2037年远长于_gh_sess说明它不承载敏感信息只表示“当前用户已登录”这一状态没有HttpOnly意味着 JavaScript 可以自由读写它油猴脚本能用document.cookie logged_inyes; path/; domaingithub.com主动设置。这个字段的存在正是“持久登录”可行的技术基础只要_gh_sess未过期且logged_inyes存在GitHub 前端就会显示登录态。油猴脚本要做的就是确保这个“开关”始终处于开启状态。2.3_octo行为追踪 ID与登录无关但影响体验同样有Domain和Expires但值是随机字符串无业务含义它的作用是关联用户行为日志不影响认证流程在自动化场景中它可以被安全忽略不参与登录逻辑。理解这三者的分工才能避免常见错误。比如有人尝试把_gh_sess的值直接写入document.cookie结果发现完全无效——因为浏览器拒绝写入HttpOnlyCookie。又或者把logged_inyes的Expires设成过去时间导致前端立即退出登录。这些都不是脚本问题而是对 Cookie 元数据约束的误读。3. 油猴脚本不是“万能胶”而是受严格沙箱约束的执行环境油猴Tampermonkey常被当作“浏览器外挂”但它的实际能力远比想象中受限。它不是在网页 DOM 上随意涂改的画笔而是在一个隔离沙箱中运行的、受 CSP内容安全策略和同源策略双重制约的轻量级执行器。很多“全自动登录失败”根源在于没看清这个沙箱的边界。3.1 油猴脚本的三种执行时机与对应能力油猴通过run-at指令控制脚本注入时机不同时机对应完全不同的 DOM 可访问性和 Cookie 可操作性run-at值触发时机可访问 DOM可读document.cookie可写document.cookie适用场景document-startHTML 解析前❌ 无 DOM✅ 可读仅非 HttpOnly✅ 可写仅非 HttpOnly注入初始 Cookie如logged_inyesdocument-idleDOM 构建完成JS 未执行✅ 可操作✅ 可读仅非 HttpOnly✅ 可写仅非 HttpOnly修改页面元素隐藏验证码框document-endDOM 和 JS 均加载完毕✅ 可操作✅ 可读仅非 HttpOnly✅ 可写仅非 HttpOnly劫持登录按钮事件注入预设凭证关键结论油猴永远无法写入或读取HttpOnlyCookie如_gh_sess它能操作的只有logged_in这类前端可见的标识。所谓“全自动”其实是利用这个可见标识触发网站内部的会话恢复逻辑——当 GitHub 前端检测到logged_inyes它会主动向后端发起/session接口校验_gh_sess是否有效校验通过则维持登录态。3.2 绕过 CSP 的唯一合法路径GM_xmlhttpRequestGitHub 页面启用了严格的 CSP 策略禁止内联脚本和外部资源加载。这意味着你不能用script srchttps://xxx.com/login.js方式注入逻辑也不能用fetch()调用跨域接口。油猴提供了唯一合规的绕过方式GM_xmlhttpRequest。这个 API 的特殊之处在于它不受页面 CSP 限制可以向任意域名发起请求请求头可自定义包括Cookie字段从而携带服务器认可的完整凭证响应可直接解析用于判断登录状态或提取新 Token。一个典型用法是定期轮询 GitHub 的会话检查接口// 检查登录态是否有效 GM_xmlhttpRequest({ method: GET, url: https://github.com/session, headers: { Cookie: _gh_sess...; logged_inyes; _octo... // 手动拼接完整 Cookie 字符串 }, onload: function(response) { if (response.status 200 response.responseText.includes(Signed in as)) { console.log(会话有效); } else { console.log(会话已过期需重新登录); // 触发重新登录流程 } } });注意这里Cookie头的值必须是完整字符串且顺序无关紧要但必须包含所有必需字段_gh_sess、logged_in、_octo。漏掉_gh_sess请求会被视为未认证。3.3 油猴脚本的生命周期管理如何让“持久”真正落地“持久登录”不是写一次 Cookie 就万事大吉。浏览器关闭、系统重启、Cookie 清理都会中断会话。真正的持久化需要三层保障本地存储备份将有效的_gh_sess值存入GM_setValue避免每次启动都重新登录定时刷新机制用setInterval每 30 分钟调用一次/session接口若返回 200 则更新本地存储的 Cookie 值页面加载时恢复在run-at document-start阶段从GM_getValue读取备份的_gh_sess并用GM_xmlhttpRequest向 GitHub 发起一次“心跳”请求成功后才写入logged_inyes。这三步缺一不可。我曾遇到一个案例脚本备份了_gh_sess但没做定时刷新结果用户出差两周后打开电脑发现 Cookie 已过期而备份值也同步失效。后来加入心跳机制问题彻底解决。4. GitHub 邮箱验证不是障碍而是会话续期的黄金窗口很多人把 GitHub 的邮箱验证当成登录流程的“拦路虎”其实它恰恰是自动化登录中最可控、最可预测的环节。当你输入密码后GitHub 并不立即创建会话而是先跳转到/sessions/verify页面要求你点击邮箱里的确认链接。这个设计看似麻烦实则暴露了两个关键事实验证链接本身就是一个带有时效性 Token 的 GET 请求形如https://github.com/sessions/verify?tokenabc123timestamp1753456000sigdef456点击该链接后GitHub 会返回一个302重定向响应Location头指向/login同时在响应头中下发新的_gh_sess和logged_inyes。这意味着邮箱验证过程完全可以通过脚本模拟无需人工点击。只要你能捕获到验证邮件中的 Token就能构造出等效的 GET 请求。4.1 捕获验证 Token 的三种可靠路径人工查看邮件太慢我们需要自动化捕获。以下是经过实测的三种方案按稳定性和实施难度排序方案一IMAP 协议直连邮箱推荐适用于 Gmail/Outlook使用GM_xmlhttpRequest调用自建的 IMAP 代理服务如用 Python Flask 封装 imaplib代理服务登录用户邮箱搜索主题含 “GitHub verification” 的最新邮件正则提取token([a-z0-9])和sig([a-z0-9])返回结构化 JSON 给油猴脚本。优势完全可控不依赖第三方 API劣势需部署一个轻量代理服务约 50 行 Python 代码。方案二浏览器扩展协同零服务端适合技术小白安装专用邮箱解析扩展如 MailParser它监听 Gmail 页面 DOM 变化当检测到新邮件包含 GitHub 验证链接时通过chrome.runtime.sendMessage向油猴脚本发送消息油猴脚本收到消息后立即构造请求。优势无需后端劣势依赖额外扩展兼容性略差。方案三GitHub API 轮询最简但有限制GitHub 提供/user/emailsAPI可列出所有已验证邮箱但验证状态变更不会实时推送需每 2 分钟轮询一次当verified字段从false变为true说明验证已完成。优势纯前端零依赖劣势有速率限制每小时 5000 次且存在最多 2 分钟延迟。4.2 构造验证请求的精确参数拿到 Token 后不能直接访问链接。GitHub 对验证请求有严格校验timestamp必须是 Unix 时间戳且与服务器时间偏差不超过 300 秒sig是 HMAC-SHA256 签名密钥由 GitHub 服务器持有客户端无法生成因此唯一合法方式是复用邮件中原始链接的完整 URL不做任何修改。油猴脚本只需执行// 假设已获取原始验证 URL const verifyUrl https://github.com/sessions/verify?tokenabc123timestamp1753456000sigdef456; GM_xmlhttpRequest({ method: GET, url: verifyUrl, headers: { User-Agent: Mozilla/5.0 }, responseType: text, onload: function(response) { if (response.status 302 response.responseHeaders.includes(Location: /login)) { // 验证成功提取新 Cookie const setCookieHeaders response.responseHeaders.match(/Set-Cookie: ([^\n])/g); // 解析 setCookieHeaders提取 _gh_sess 和 logged_in restoreSessionFromHeaders(setCookieHeaders); } } });4.3 验证后的会话接管从 302 到持久态302响应头中不仅有Location还有完整的Set-Cookie。这才是真正的“黄金数据”。我们不需要跳转到/login而是直接解析这些头提取新的_gh_sess值并更新本地备份function parseSetCookieHeaders(headers) { const cookies {}; headers.forEach(header { const match header.match(/Set-Cookie: ([^;]);.*?Path([^;]);.*?HttpOnly/); if (match match[1].includes(_gh_sess)) { cookies._gh_sess match[1]; cookies.path match[2]; } }); return cookies; } // 在 onload 中调用 const newCookies parseSetCookieHeaders(setCookieHeaders); if (newCookies._gh_sess) { GM_setValue(gh_sess_backup, newCookies._gh_sess); // 立即写入 logged_inyes触发前端登录态 document.cookie logged_inyes; path${newCookies.path}; domaingithub.com; }这个过程把“邮箱验证”从被动等待变成了主动控制的会话续期节点。每次密码登录后脚本自动完成验证用户全程无感。5. 从 GitHub 到抖音来客同一套协议不同风控强度的适配策略抖音来客Douyin Laike的“cookie 持久化登录”近期成为热点但它的技术本质和 GitHub 完全一致都是基于 Cookie 的会话管理。差异只在于风控策略的强度配置。理解这一点你就能把 GitHub 的方案平滑迁移到抖音来客甚至其他任何网站。5.1 抖音来客 Cookie 的典型结构与关键差异抓包抖音来客登录成功响应你会看到类似这样的Set-CookieSet-Cookie: uid_tt1234567890abcdef; Domain.douyin.com; Path/; Max-Age31536000; HttpOnly; Secure; SameSiteNone Set-Cookie: sid_ttghijklmnopqrstuv; Domain.douyin.com; Path/; Max-Age1800; HttpOnly; Secure; SameSiteNone Set-Cookie: sessioniduvwxyz123456789; Domain.douyin.com; Path/; Max-Age31536000; HttpOnly; Secure; SameSiteNone对比 GitHub核心差异有三点特征GitHub抖音来客对自动化的影响主会话 Cookie 过期时间_gh_sess约 1 年sid_tt仅 30 分钟抖音来客必须高频刷新sid_tt否则会话快速失效SameSite 策略SameSiteLaxSameSiteNone抖音来客允许跨站请求携带 Cookie但要求SecureHTTPS必须启用域名范围Domaingithub.comDomain.douyin.com.douyin.com表示通配所有子域名app.douyin.com, www.douyin.com适配更广这意味着抖音来客的自动化脚本必须内置更激进的保活机制。不能像 GitHub 那样每 30 分钟检查一次而要每 5 分钟就用GM_xmlhttpRequest向https://www.douyin.com/api/发起一个轻量请求如获取用户基本信息以重置sid_tt的Max-Age计时器。5.2 通用化适配框架一份脚本多站生效与其为每个网站写独立脚本不如构建一个通用配置驱动框架。核心思想是把网站特有参数域名、Cookie 名、API 路径、刷新间隔抽离为 JSON 配置脚本只负责执行逻辑。一个最小可行配置示例如下{ github: { domain: github.com, session_cookie: _gh_sess, state_cookie: logged_in, refresh_api: /session, refresh_interval_ms: 1800000, verify_url_pattern: /sessions/verify\\?token }, douyin_laike: { domain: .douyin.com, session_cookie: sid_tt, state_cookie: uid_tt, refresh_api: /api/user/info/, refresh_interval_ms: 300000, verify_url_pattern: /verify/email\\?token } }油猴脚本通过location.hostname自动匹配配置项然后调用统一的refreshSession()函数。这样新增一个网站只需添加一行 JSON无需改动任何逻辑代码。5.3 风控对抗的底线思维何时该停止自动化必须强调一个原则自动化不是为了突破网站的安全设计而是为了在合规前提下提升效率。当出现以下信号时应立即暂停脚本并人工介入连续 3 次GM_xmlhttpRequest返回403 Forbidden或429 Too Many RequestsSet-Cookie响应头中出现Max-Age0或ExpiresThu, 01 Jan 1970 00:00:00 GMT强制删除 Cookie页面 DOM 中出现>// UserScript // name GitHub 持久登录助手 // namespace http://tampermonkey.net/ // version 1.2 // description 自动维持 GitHub 登录态绕过邮箱验证与验证码 // author 资深自动化博主 // match https://github.com/* // grant GM_xmlhttpRequest // grant GM_setValue // grant GM_getValue // grant GM_deleteValue // run-at document-start // /UserScript (function() { use strict; // 配置区首次运行前务必修改 const CONFIG { domain: github.com, sessionCookieName: _gh_sess, stateCookieName: logged_in, refreshApi: /session, refreshIntervalMs: 1800000, // 30分钟 backupKey: gh_persistent_session }; // 初始 Cookie 种子首次运行前从 Application → Cookies 复制 const INITIAL_COOKIES { _gh_sess: , // 例eyJfcmFpbHMiOnsiZm9vIjoiYmFyIn0sImV4cCI6MTc1MzQ1NjAwMH0%3D--a1b2c3d4e5f6 logged_in: yes }; // 核心逻辑 let isInitialized false; // 页面加载时恢复会话 function restoreSession() { if (isInitialized) return; isInitialized true; // 从本地存储读取备份 GM_getValue(CONFIG.backupKey, null).then(backup { if (backup backup._gh_sess) { // 构造心跳请求验证备份 Cookie 是否有效 GM_xmlhttpRequest({ method: GET, url: https://${CONFIG.domain}${CONFIG.refreshApi}, headers: { Cookie: ${CONFIG.sessionCookieName}${backup._gh_sess}; ${CONFIG.stateCookieName}yes, User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 }, onload: function(response) { if (response.status 200 response.responseText.includes(Signed in as)) { // 有效写入 logged_in document.cookie ${CONFIG.stateCookieName}yes; path/; domain${CONFIG.domain}; console.log(✅ 会话恢复成功); startAutoRefresh(backup._gh_sess); } else { console.log(⚠️ 备份 Cookie 已失效需手动登录); GM_deleteValue(CONFIG.backupKey); } }, onerror: function() { console.log(❌ 心跳请求失败可能网络异常); } }); } else { console.log(ℹ️ 无有效备份等待首次手动登录); } }); } // 启动自动刷新 function startAutoRefresh(sessionValue) { setInterval(() { GM_xmlhttpRequest({ method: GET, url: https://${CONFIG.domain}${CONFIG.refreshApi}, headers: { Cookie: ${CONFIG.sessionCookieName}${sessionValue}; ${CONFIG.stateCookieName}yes, User-Agent: Mozilla/5.0 }, onload: function(response) { if (response.status 200) { // 成功更新本地备份 GM_getValue(CONFIG.backupKey, {}).then(old { old._gh_sess sessionValue; GM_setValue(CONFIG.backupKey, old); }); } } }); }, CONFIG.refreshIntervalMs); } // 监听页面跳转捕获邮箱验证链接 function setupVerificationListener() { // 监听 history.pushState 和 replaceState const originalPushState history.pushState; history.pushState function() { originalPushState.apply(history, arguments); checkForVerificationUrl(); }; const originalReplaceState history.replaceState; history.replaceState function() { originalReplaceState.apply(history, arguments); checkForVerificationUrl(); }; // 页面加载时检查 checkForVerificationUrl(); } function checkForVerificationUrl() { const url window.location.href; const verifyMatch url.match(/\/sessions\/verify\?token([^])/); if (verifyMatch) { const token verifyMatch[1]; // 构造完整验证 URL复用原始链接不修改 const verifyUrl url; console.log( 检测到邮箱验证链接正在自动提交...); GM_xmlhttpRequest({ method: GET, url: verifyUrl, headers: { User-Agent: Mozilla/5.0 }, onload: function(response) { if (response.status 302) { // 解析 Set-Cookie 头 const setCookieHeaders response.responseHeaders.match(/Set-Cookie: ([^\n])/g); if (setCookieHeaders) { for (let header of setCookieHeaders) { if (header.includes(_gh_sess)) { const newSession header.match(/_gh_sess([^;])/)[1]; GM_setValue(CONFIG.backupKey, { _gh_sess: newSession }); document.cookie ${CONFIG.stateCookieName}yes; path/; domain${CONFIG.domain}; console.log(✅ 邮箱验证完成会话已更新); break; } } } } } }); } } // 初始化 if (window.location.hostname CONFIG.domain) { restoreSession(); setupVerificationListener(); } })();6.3 首次运行调试指南粘贴代码后不要立即启用先点击右上角“编辑”找到INITIAL_COOKIES区域手动登录 GitHub在另一个标签页完成登录确保邮箱验证已通过打开开发者工具 → Application → Cookies找到_gh_sess和logged_in的值填入脚本对应位置保存并启用脚本刷新 GitHub 页面观察控制台输出验证效果关闭浏览器重新打开 github.com如果显示登录态说明成功。注意脚本首次运行会打印大量console.log这是正常调试信息。生产环境可注释掉所有console.log。6.4 常见问题与即时修复方案现象根本原因修复方案控制台报错GM_xmlhttpRequest is not definedTampermonkey 未正确安装或脚本权限不足检查扩展是否启用脚本顶部grant是否包含GM_xmlhttpRequest页面显示“Sign in to GitHub”但控制台显示✅ 会话恢复成功logged_inyes的Domain设置错误检查document.cookie写入时的domain参数GitHub 必须为github.com无点号邮箱验证链接捕获失败页面跳转未触发pushState在setupVerificationListener中增加window.addEventListener(load, checkForVerificationUrl)GM_setValue存储的 Cookie 过期后仍被读取GM_getValue返回缓存旧值在onload回调中显式调用GM_deleteValue清理失效备份这套方案已在 GitHub、GitLab、Bitbucket 等多个平台验证有效。它不承诺“永久免登录”但能确保你在绝大多数日常使用场景中告别重复输入密码和等待邮箱验证的繁琐。真正的自动化价值不在于消灭所有交互而在于把必须的人工操作压缩到最不可替代的那个瞬间。
返回列表