全栈开发

google插件获取cookie示例

方案1:

manifest.json

{
  "manifest_version": 3,
  "name": "Local Cookie Tester - Zhihu",
  "version": "1.0",
  "description": "Test reading Zhihu login cookies locally.",

  "host_permissions": [
    "https://*.zhihu.com/*"
  ],

  "background": {
    "service_worker": "background.js"
  },

  "action": {
    "default_popup": "popup.html",
    "default_title": "Capture Zhihu Cookie"
  },

  "permissions": ["cookies"]
}

background.js

// background.js
const SUPPORTED_SITES = {
  zhihu: {
    name: '知乎',
    domains: ['.zhihu.com']  // 知乎 Cookie 域名为 .zhihu.com
  }
};

chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  if (request.action === 'captureCookies') {
    const { siteKey } = request;
    const site = SUPPORTED_SITES[siteKey];

    if (!site) {
      sendResponse({ success: false, error: 'Unsupported site' });
      return true;
    }

    let allCookies = [];
    let pending = site.domains.length;

    const checkDone = () => {
      if (--pending === 0) {
        sendResponse({
          success: true,
          siteName: site.name,
          cookies: allCookies
        });
      }
    };

    if (pending === 0) {
      sendResponse({ success: false, error: 'No domains' });
      return true;
    }

    site.domains.forEach(domain => {
      chrome.cookies.getAll({ domain }, (cookies) => {
        const plainCookies = cookies.map(c => ({
          name: c.name,
          value: c.value,
          domain: c.domain,
          path: c.path,
          secure: c.secure,
          httpOnly: c.httpOnly,
          expires: c.expirationDate ? new Date(c.expirationDate * 1000).toLocaleString() : 'Session'
        }));
        allCookies = allCookies.concat(plainCookies);
        checkDone();
      });
    });

    return true;
  }
});

popup.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <style>
    body {
      width: 320px;
      padding: 12px;
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
      font-size: 13px;
    }
    .btn {
      display: block;
      width: 100%;
      padding: 8px;
      margin: 6px 0;
      background: #0084ff; /* 知乎品牌色 */
      color: white;
      border: none;
      border-radius: 4px;
      cursor: pointer;
      font-size: 14px;
    }
    .btn:hover {
      background: #0066cc;
    }
    .result {
      margin-top: 12px;
      max-height: 300px;
      overflow-y: auto;
      white-space: pre-wrap;
      background: #f8f9fa;
      padding: 8px;
      border-radius: 4px;
      font-family: monospace;
      font-size: 11px;
    }
    .status {
      margin-top: 8px;
      color: #d93025;
      font-weight: bold;
    }
  </style>
</head>
<body>
  <!-- 仅保留知乎按钮 -->
  <button class="btn" data-site="zhihu">🔍 捕获知乎 Cookie</button>
  
  <div class="status" id="status"></div>
  <div class="result" id="result"></div>

  <script src="popup.js"></script>
</body>
</html>

popup.js

// popup.js
// 调用 background 读取 Cookie,并在本地弹窗显示

document.querySelectorAll('.btn').forEach(btn => {
  btn.addEventListener('click', () => {
    const siteKey = btn.dataset.site;
    const statusEl = document.getElementById('status');
    const resultEl = document.getElementById('result');
    
    statusEl.textContent = '正在读取...';
    resultEl.textContent = '';

    // 发消息给 background
    chrome.runtime.sendMessage(
      { action: 'captureCookies', siteKey },
      (response) => {
        if (chrome.runtime.lastError) {
          statusEl.textContent = '❌ 扩展错误: ' + chrome.runtime.lastError.message;
          return;
        }

        if (response.success) {
          statusEl.textContent = `✅ 成功捕获 ${response.siteName} Cookie!`;
          
          if (response.cookies.length === 0) {
            resultEl.textContent = '⚠️ 未找到 Cookie(你可能未登录?)';
          } else {
            // 格式化为易读字符串
            const lines = response.cookies.map(c => 
              `${c.domain}${c.path} | ${c.name}=${c.value} | Secure:${c.secure} | HttpOnly:${c.httpOnly} | Expires:${c.expires}`
            );
            resultEl.textContent = lines.join('\n\n');
          }
        } else {
          statusEl.textContent = '❌ 失败: ' + (response.error || '未知错误');
        }
      }
    );
  });
});
  1. 打开 Chrome → 地址栏输入:chrome://extensions/
  2. 开启右上角「开发者模式」
  3. 点击「加载已解压的扩展程序」 → 选择本项目文件夹
  4. 手动访问 https://zhihu.com → 完成登录
  5. 点击浏览器右上角扩展图标 → 点击 “捕获知乎 Cookie”

方案2:手动登陆版

1、background.js

// background.js

const SUPPORTED_SITES = {
    zhihu: { domains: ['.zhihu.com'] },
    netease: { domains: ['.163.com'] },
    baidu: { domains: ['.baidu.com'] }
};

const MOCK_ACCOUNTS = {
    zhihu: { username: 'zhihu_user@example.com', password: 'ZhihuPass123!' },
    netease: { username: 'netease_user@163.com', password: 'NeteasePass456!' },
    baidu: { username: 'baidu_user', password: 'BaiduPass789!' }
};

chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
    if (request.action === 'captureCookiesFromOverlay') {
        const { siteKey } = request;
        const site = SUPPORTED_SITES[siteKey];

        if (!site) {
            sendResponse({ success: false });
            return true;
        }

        let allCookies = [];
        let pending = site.domains.length;

        const done = () => {
            if (--pending === 0) {
                // ✅ 关键:将 Cookie 对象转为普通 JS 对象(可安全 JSON.stringify)
                const plainCookies = allCookies.map(c => ({
                    name: c.name,
                    value: c.value,
                    domain: c.domain,
                    path: c.path,
                    secure: c.secure,
                    httpOnly: c.httpOnly,
                    // expirationDate 是时间戳(秒),可选转换
                    expirationDate: c.expirationDate
                }));
                console.log('Sending POST to /api/auth/callback');
                // 发送到 Spring Boot
                fetch('http://localhost:8080/api/auth/callback', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({
                        site: siteKey,
                        cookies: plainCookies // ✅ 使用 plainCookies
                    })
                })
                    .then(response => response.json())
                    .then(() => {
                        sendResponse({ success: true });
                    })
                    .catch(err => {
                        console.error('Send failed:', err);
                        sendResponse({ success: false, error: err.message });
                    });
            }
        };

        if (pending === 0) {
            sendResponse({ success: false });
            return true;
        }

        site.domains.forEach(domain => {
            chrome.cookies.getAll({ domain }, (cookies) => {
                allCookies.push(...cookies);
                done();
            });
        });

        return true;
    } // ✅ 新增:关闭当前标签页
    else if (request.action === 'closeCurrentTab') {
        if (sender.tab?.id) {
            chrome.tabs.remove(sender.tab.id, () => {
                // 可选:处理关闭失败(通常不需要)
                if (chrome.runtime.lastError) {
                    console.warn('Failed to close tab:', chrome.runtime.lastError);
                }
            });
        }
        sendResponse({ success: true });
        return true;
    } // ✅ 新增:获取账号信息
    else if (request.action === 'getAccountInfo') {
        const { siteKey } = request;
        const account = MOCK_ACCOUNTS[siteKey] || null;
        sendResponse({ success: !!account, account });
        return true;
    }

    return true; // 保持其他消息通道开放
});

2、content.js

(() => {
  if (document.getElementById('auth-dock-root')) return;

  const escapeHtml = (str) => {
    if (typeof str !== 'string') return '';
    return str.replace(/[&<>"']/g, (match) => ({
      '&': '&',
      '<': '<',
      '>': '>',
      '"': '"',
      "'": '''
    })[match]);
  };

  const getSiteKey = () => {
    const host = window.location.hostname;
    if (host.endsWith('.zhihu.com')) return 'zhihu';
    if (host.endsWith('.163.com')) return 'netease';
    if (host.endsWith('.baidu.com')) return 'baidu';
    return null;
  };

  const siteKey = getSiteKey();
  if (!siteKey) return;

  const SITE_NAMES = {
    zhihu: '知乎',
    netease: '网易',
    baidu: '百度'
  };

  // ✅ 定义每个站点的“验证页面”跳转地址(必须是登录后才能访问的页面)
  const getVerificationUrl = (siteKey) => {
    switch (siteKey) {
      case 'zhihu': return 'https://www.zhihu.com/settings/account';
      case 'netease': return 'https://reg.163.com/account/info';
      case 'baidu': return 'https://www.baidu.com/my/index';
      default: return null;
    }
  };

  // ✅ 判断当前页面是否属于“已登录验证页”
  const isOnVerificationPage = (siteKey, url) => {
    const verificationUrl = getVerificationUrl(siteKey);
    if (!verificationUrl) return false;

    try {
      const current = new URL(url);
      const target = new URL(verificationUrl);

      // 必须同源
      if (current.origin !== target.origin) return false;

      // 路径必须以 target 路径开头(例如 /my/index → 匹配 /my/index 或 /my/index?xxx)
      return current.pathname === target.pathname || current.pathname.startsWith(target.pathname + '/');
    } catch (e) {
      console.warn('URL parse error in isOnVerificationPage:', e);
      return false;
    }
  };

  // 创建工具栏
  const root = document.createElement('div');
  root.id = 'auth-dock-root';
  root.innerHTML = `
    <button class="auth-dock-btn" data-action="capture">✅ 授权</button>
    <button class="auth-dock-btn" data-action="account">👤 账号</button>
    <button class="auth-dock-btn" data-action="close">🚪 退出</button>
  `;

  const style = document.createElement('style');
  style.textContent = `
    #auth-dock-root {
      position: fixed;
      right: 20px;
      top: 50%;
      transform: translateY(-50%);
      display: flex;
      flex-direction: column;
      gap: 8px;
      z-index: 2147483646;
      pointer-events: auto;
    }

    .auth-dock-btn {
      width: 76px;
      height: 32px;
      font-size: 12px;
      border: none;
      border-radius: 6px;
      background: rgba(59, 130, 246, 0.85);
      color: white;
      cursor: pointer;
      font-family: -apple-system, BlinkMacSystemFont, sans-serif;
      opacity: 0.7;
      transition: opacity 0.2s, background-color 0.2s;
      box-shadow: 0 2px 4px rgba(0,0,0,0.2);
      user-select: none;
    }

    .auth-dock-btn:hover {
      opacity: 1;
      background: rgba(59, 130, 246, 1);
    }

    .auth-dock-btn:disabled {
      opacity: 0.5;
      cursor: not-allowed;
    }

    /* 账号模态框 */
    #account-modal {
      position: fixed;
      top: 0;
      left: 0;
      width: 100vw;
      height: 100vh;
      background: rgba(0, 0, 0, 0.5);
      display: flex;
      justify-content: center;
      align-items: center;
      z-index: 2147483647;
    }

    #account-modal-content {
      background: white;
      padding: 20px;
      border-radius: 8px;
      width: 300px;
      box-shadow: 0 4px 20px rgba(0,0,0,0.3);
      font-family: sans-serif;
    }

    .account-field {
      display: flex;
      align-items: center;
      margin-bottom: 12px;
    }

    .account-label {
      width: 60px;
      font-weight: bold;
      color: #333;
    }

    .account-value {
      flex: 1;
      padding: 4px 8px;
      background: #f8f9fa;
      border: 1px solid #ddd;
      border-radius: 4px;
      font-size: 14px;
      word-break: break-all;
    }

    .copy-btn {
      margin-left: 8px;
      padding: 4px 8px;
      background: #3b82f6;
      color: white;
      border: none;
      border-radius: 4px;
      cursor: pointer;
      font-size: 12px;
    }

    .copy-btn:hover {
      background: #2563eb;
    }

    .modal-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 16px;
    }

    .modal-title {
      font-size: 16px;
      font-weight: bold;
    }

    .modal-close {
      background: none;
      border: none;
      font-size: 18px;
      cursor: pointer;
      color: #999;
    }
  `;
  document.head.appendChild(style);
  document.body.appendChild(root);

  // 关闭当前标签页
  const closeCurrentTab = () => {
    chrome.runtime.sendMessage({ action: 'closeCurrentTab' });
  };

  // 显示账号信息
  const showAccountModal = async () => {
    const response = await new Promise(resolve => {
      chrome.runtime.sendMessage({ action: 'getAccountInfo', siteKey }, resolve);
    });

    if (!response?.success) {
      alert('❌ 未配置账号信息');
      return;
    }

    const { username, password } = response.account;
    const safeUsername = escapeHtml(username);
    const safePassword = escapeHtml(password);

    const modal = document.createElement('div');
    modal.id = 'account-modal';
    modal.innerHTML = `
      <div id="account-modal-content">
        <div class="modal-header">
          <div class="modal-title">👤 ${escapeHtml(SITE_NAMES[siteKey])} 账号</div>
          <button class="modal-close">×</button>
        </div>
        <div class="account-field">
          <div class="account-label">用户名:</div>
          <div class="account-value">${safeUsername}</div>
          <button class="copy-btn" data-copy="${safeUsername}">复制</button>
        </div>
        <div class="account-field">
          <div class="account-label">密码:</div>
          <div class="account-value">${safePassword}</div>
          <button class="copy-btn" data-copy="${safePassword}">复制</button>
        </div>
      </div>
    `;

    modal.querySelector('.modal-close').addEventListener('click', () => {
      document.body.removeChild(modal);
    });

    modal.querySelectorAll('.copy-btn').forEach(btn => {
      btn.addEventListener('click', () => {
        const text = btn.dataset.copy;
        navigator.clipboard.writeText(text).then(() => {
          const original = btn.textContent;
          btn.textContent = '✅ 已复制';
          setTimeout(() => btn.textContent = original, 1500);
        }).catch(() => {
          alert('复制失败,请手动复制');
        });
      });
    });

    document.body.appendChild(modal);
  };

  // 绑定按钮事件
  root.querySelectorAll('.auth-dock-btn').forEach(btn => {
    btn.addEventListener('click', () => {
      const action = btn.dataset.action;

      if (action === 'capture') {
        const currentUrl = window.location.href;
        const verificationUrl = getVerificationUrl(siteKey);
        console.log("currentUrl: " + currentUrl);
        // ✅ 校验是否在验证页面
        if (!isOnVerificationPage(siteKey, currentUrl)) {
          if (verificationUrl) {
            const confirmed = confirm(
              `⚠️ 为确保授权有效,请先跳转到 ${SITE_NAMES[siteKey]} 的个人页面。\n是否立即跳转?`
            );
            if (confirmed) {
              window.location.href = verificationUrl;
            }
          } else {
            alert('❌ 未配置该站点的验证页面');
          }
          return;
        }

        // ✅ 已在验证页,执行授权
        btn.disabled = true;
        btn.textContent = '捕获中…';

        chrome.runtime.sendMessage(
          { action: 'captureCookiesFromOverlay', siteKey },
          (response) => {
            if (response?.success) {
              btn.textContent = '✅ 成功';
              setTimeout(closeCurrentTab, 1500);
            } else {
              btn.textContent = '❌ 失败';
              setTimeout(() => {
                btn.disabled = false;
                btn.textContent = '✅ 授权';
              }, 2000);
            }
          }
        );
      } else if (action === 'account') {
        showAccountModal();
      } else if (action === 'close') {
        closeCurrentTab();
      }
    });
  });
})();

3、manifest.json

{
  "manifest_version": 3,
  "name": "Auth Overlay",
  "version": "1.0",
  "description": "Show auth overlay on target sites.",

  "host_permissions": [
    "https://*.zhihu.com/*",
    "https://*.163.com/*",
    "https://*.baidu.com/*"
  ],

  "background": {
    "service_worker": "background.js"
  },

  "content_scripts": [
    {
      "matches": [
        "https://*.zhihu.com/*",
        "https://*.163.com/*",
        "https://*.baidu.com/*"
      ],
      "js": ["content.js"],
      "run_at": "document_idle"
    }
  ],

  "permissions": ["cookies", "tabs"]
}

springboot api示例

package press.huang.demo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

@Configuration
public class CorsConfig {

    @Bean
    public CorsFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true); // 支持 credentials(如 cookies)
        config.addAllowedOriginPattern("*"); // Spring Boot 2.4+ 用 originPatterns
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }
}
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/api")
public class CookieController {

    // 定义接收的数据结构
    public static class CookieDto {
        public String name;
        public String value;
        public String domain;
        public String path;
        public Boolean secure;
        public Boolean httpOnly;
        public String expires;
    }

    public static class CookieRequest {
        public String site;
        public List<CookieDto> cookies;
    }

    @PostMapping("/auth/callback")
    public Map<String, Object> receiveCookies(@RequestBody CookieRequest request) {
        System.out.println("========== 收到 Cookie 数据 ==========");
        System.out.println("站点: " + request.site);
        System.out.println("Cookie 数量: " + request.cookies.size());
        System.out.println("----------------------------------");

        for (CookieDto cookie : request.cookies) {
            System.out.printf(
                    "名称: %-15s : %-30s 域名: %-15s HttpOnly: %s%n",
                    cookie.name,
                    cookie.value.substring(0, Math.min(30, cookie.value.length())) + (cookie.value.length() > 30 ? "..." : ""),
                    cookie.domain,
                    cookie.httpOnly
            );
        }

        System.out.println("====================================\n");

        // 返回成功响应(扩展需要)
        Map<String, Object> response = new HashMap<>();
        response.put("success", true);
        response.put("message", "Cookie received");
        return response;
    }
}

方案3:自动登陆版

1、background.js

// background.js

const SUPPORTED_SITES = {
    zhihu: { domains: ['.zhihu.com'] },
    netease: { domains: ['.163.com'] },
    baidu: { domains: ['.baidu.com'] }
};



chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
    if (request.action === 'captureCookiesFromOverlay') {
        const { siteKey } = request;
        const site = SUPPORTED_SITES[siteKey];

        if (!site) {
            sendResponse({ success: false });
            return true;
        }

        let allCookies = [];
        let pending = site.domains.length;

        const done = () => {
            if (--pending === 0) {
                const plainCookies = allCookies.map(c => ({
                    name: c.name,
                    value: c.value,
                    domain: c.domain,
                    path: c.path,
                    secure: c.secure,
                    httpOnly: c.httpOnly,
                    expirationDate: c.expirationDate
                }));

                console.log('Sending POST to /api/auth/callback');
                fetch('http://localhost:8080/api/auth/callback', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({
                        site: siteKey,
                        cookies: plainCookies
                    })
                })
                    .then(response => response.json())
                    .then(() => {
                        sendResponse({ success: true });
                    })
                    .catch(err => {
                        console.error('Send failed:', err);
                        sendResponse({ success: false, error: err.message });
                    });
            }
        };

        if (pending === 0) {
            sendResponse({ success: false });
            return true;
        }

        site.domains.forEach(domain => {
            chrome.cookies.getAll({ domain }, (cookies) => {
                allCookies.push(...cookies);
                done();
            });
        });

        return true;
    }
    // ✅ 关闭当前标签页
    else if (request.action === 'closeCurrentTab') {
        if (sender.tab?.id) {
            chrome.tabs.remove(sender.tab.id, () => {
                if (chrome.runtime.lastError) {
                    console.warn('Failed to close tab:', chrome.runtime.lastError);
                }
            });
        }
        sendResponse({ success: true });
        return true;
    }
    // ✅ 从 Spring Boot 获取账号信息(关键修改)
    else if (request.action === 'getAccountInfo') {
        const { siteKey } = request;

        // ✅ 向你的 Spring Boot 接口请求账号
        fetch(`http://localhost:8080/api/auth/account/${siteKey}`)
            .then(response => {
                if (!response.ok) {
                    throw new Error(`HTTP ${response.status}`);
                }
                return response.json();
            })
            .then(data => {
                if (data.success && data.account) {
                    sendResponse({ success: true, account: data.account });
                } else {
                    sendResponse({ success: false, error: data.error || 'Account not available' });
                }
            })
            .catch(err => {
                console.error('Failed to fetch account from Spring Boot:', err);
                sendResponse({ success: false, error: 'Network or server error' });
            });

        // ✅ 必须返回 true 以保持异步响应通道
        return true;
    }

    return true;
});

2、content.js

(() => {
  if (document.getElementById('auth-dock-root')) return;

  const escapeHtml = (str) => {
    if (typeof str !== 'string') return '';
    return str.replace(/[&<>"']/g, (match) => ({
      '&': '&',
      '<': '<',
      '>': '>',
      '"': '"',
      "'": '''
    })[match]);
  };

  const getSiteKey = () => {
    const host = window.location.hostname;
    if (host.endsWith('.zhihu.com')) return 'zhihu';
    if (host.endsWith('.163.com')) return 'netease';
    if (host.endsWith('.baidu.com')) return 'baidu';
    return null;
  };

  const siteKey = getSiteKey();
  if (!siteKey) return;

  const SITE_NAMES = {
    zhihu: '知乎',
    netease: '网易',
    baidu: '百度'
  };

  // ✅ 验证页面配置
  const getVerificationUrl = (siteKey) => {
    switch (siteKey) {
      case 'zhihu': return 'https://www.zhihu.com/settings/account';
      case 'netease': return 'https://reg.163.com/account/info';
      case 'baidu': return 'https://www.baidu.com/my/index';
      default: return null;
    }
  };

  const isOnVerificationPage = (siteKey, url) => {
    const verificationUrl = getVerificationUrl(siteKey);
    if (!verificationUrl) return false;
    try {
      const current = new URL(url);
      const target = new URL(verificationUrl);
      return current.origin === target.origin &&
        (current.pathname === target.pathname || current.pathname.startsWith(target.pathname + '/'));
    } catch (e) {
      return false;
    }
  };

  // ======================
  // 自动登录策略(按站点)
  // ======================
  const autoLoginStrategies = {
    baidu: autoLoginBaidu,
    zhihu: autoLoginZhihu,
    netease: autoLoginNetease
  };

  function autoLoginBaidu(account) {
    const userField = document.querySelector('#TANGRAM__PSP_3__userName');
    const passField = document.querySelector('#TANGRAM__PSP_3__password');
    const submitBtn = document.querySelector('#TANGRAM__PSP_3__submit');

    if (!userField || !passField || !submitBtn) {
      throw new Error('未检测到百度登录表单(请确保在登录页)');
    }

    userField.value = account.username;
    passField.value = account.password;

    ['input', 'change'].forEach(type => {
      userField.dispatchEvent(new Event(type, { bubbles: true }));
      passField.dispatchEvent(new Event(type, { bubbles: true }));
    });

    setTimeout(() => submitBtn.click(), 500);
    return true;
  }

  function autoLoginZhihu(account) {
    // 1. 确保切换到“密码登录”
    const pwdTab = Array.from(document.querySelectorAll('.SignFlow-tab'))
      .find(tab => tab.textContent?.trim() === '密码登录');

    if (pwdTab && !pwdTab.classList.contains('SignFlow-tab--active')) {
      pwdTab.click();
      setTimeout(() => startFilling(), 600); // 等待标签切换完成
      return;
    }

    startFilling();

    function startFilling() {
      const usernameInput = document.querySelector('input[placeholder="手机号或邮箱"]');
      const passwordInput = document.querySelector('input[placeholder="密码"]');
      const submitBtn = document.querySelector('button.SignFlow-submitButton[type="submit"]');

      if (!usernameInput || !passwordInput || !submitBtn) {
        throw new Error('未找到完整的密码登录表单');
      }

      // 2. 先填用户名
      usernameInput.value = account.username;
      usernameInput.dispatchEvent(new Event('input', { bubbles: true, cancelable: true }));
      usernameInput.dispatchEvent(new Event('change', { bubbles: true }));

      // 3. 1秒后填密码(模拟人工操作节奏)
      setTimeout(() => {
        passwordInput.value = account.password;
        passwordInput.dispatchEvent(new Event('input', { bubbles: true, cancelable: true }));
        passwordInput.dispatchEvent(new Event('change', { bubbles: true }));

        // 4. 再等 300ms 确保状态稳定,然后点击登录
        setTimeout(() => {
          if (!submitBtn.disabled) {
            console.log('🖱️ 点击登录按钮');
            submitBtn.click();
          } else {
            console.warn('⚠️ 登录按钮仍为 disabled,可能状态未同步');
            // 可选:强制点击(有些站点即使 disabled 也能提交)
            submitBtn.click();
          }
        }, 300);
      }, 1000); // ← 关键:用户名填完后等 1 秒再填密码
    }
  }

  function autoLoginNetease(account) {
    throw new Error('网易自动登录暂未实现');
  }

  // 创建工具栏
  const root = document.createElement('div');
  root.id = 'auth-dock-root';
  root.innerHTML = `
    <button class="auth-dock-btn" data-action="capture">✅ 授权</button>
    <button class="auth-dock-btn" data-action="autologin">🤖 自动</button>
    <button class="auth-dock-btn" data-action="close">🚪 退出</button>
  `;

  const style = document.createElement('style');
  style.textContent = `
    #auth-dock-root {
      position: fixed;
      right: 20px;
      top: 50%;
      transform: translateY(-50%);
      display: flex;
      flex-direction: column;
      gap: 8px;
      z-index: 2147483646;
      pointer-events: auto;
    }

    .auth-dock-btn {
      width: 76px;
      height: 32px;
      font-size: 12px;
      border: none;
      border-radius: 6px;
      background: rgba(59, 130, 246, 0.85);
      color: white;
      cursor: pointer;
      font-family: -apple-system, BlinkMacSystemFont, sans-serif;
      opacity: 0.7;
      transition: opacity 0.2s, background-color 0.2s;
      box-shadow: 0 2px 4px rgba(0,0,0,0.2);
      user-select: none;
    }

    .auth-dock-btn:hover {
      opacity: 1;
      background: rgba(59, 130, 246, 1);
    }

    .auth-dock-btn:disabled {
      opacity: 0.5;
      cursor: not-allowed;
    }

    #account-modal {
      position: fixed;
      top: 0;
      left: 0;
      width: 100vw;
      height: 100vh;
      background: rgba(0, 0, 0, 0.5);
      display: flex;
      justify-content: center;
      align-items: center;
      z-index: 2147483647;
    }

    #account-modal-content {
      background: white;
      padding: 20px;
      border-radius: 8px;
      width: 300px;
      box-shadow: 0 4px 20px rgba(0,0,0,0.3);
      font-family: sans-serif;
    }

    .account-field {
      display: flex;
      align-items: center;
      margin-bottom: 12px;
    }

    .account-label {
      width: 60px;
      font-weight: bold;
      color: #333;
    }

    .account-value {
      flex: 1;
      padding: 4px 8px;
      background: #f8f9fa;
      border: 1px solid #ddd;
      border-radius: 4px;
      font-size: 14px;
      word-break: break-all;
    }

    .copy-btn {
      margin-left: 8px;
      padding: 4px 8px;
      background: #3b82f6;
      color: white;
      border: none;
      border-radius: 4px;
      cursor: pointer;
      font-size: 12px;
    }

    .copy-btn:hover {
      background: #2563eb;
    }

    .modal-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 16px;
    }

    .modal-title {
      font-size: 16px;
      font-weight: bold;
    }

    .modal-close {
      background: none;
      border: none;
      font-size: 18px;
      cursor: pointer;
      color: #999;
    }
  `;
  document.head.appendChild(style);
  document.body.appendChild(root);

  const closeCurrentTab = () => {
    chrome.runtime.sendMessage({ action: 'closeCurrentTab' });
  };

  const showAccountModal = async () => {
    const response = await new Promise(resolve => {
      chrome.runtime.sendMessage({ action: 'getAccountInfo', siteKey }, resolve);
    });

    if (!response?.success) {
      alert('❌ 未配置账号信息');
      return;
    }

    const { username, password } = response.account;
    const safeUsername = escapeHtml(username);
    const safePassword = escapeHtml(password);

    const modal = document.createElement('div');
    modal.id = 'account-modal';
    modal.innerHTML = `
      <div id="account-modal-content">
        <div class="modal-header">
          <div class="modal-title">👤 ${escapeHtml(SITE_NAMES[siteKey])} 账号</div>
          <button class="modal-close">×</button>
        </div>
        <div class="account-field">
          <div class="account-label">用户名:</div>
          <div class="account-value">${safeUsername}</div>
          <button class="copy-btn" data-copy="${safeUsername}">复制</button>
        </div>
        <div class="account-field">
          <div class="account-label">密码:</div>
          <div class="account-value">${safePassword}</div>
          <button class="copy-btn" data-copy="${safePassword}">复制</button>
        </div>
      </div>
    `;

    modal.querySelector('.modal-close').addEventListener('click', () => {
      document.body.removeChild(modal);
    });

    modal.querySelectorAll('.copy-btn').forEach(btn => {
      btn.addEventListener('click', () => {
        const text = btn.dataset.copy;
        navigator.clipboard.writeText(text).then(() => {
          const original = btn.textContent;
          btn.textContent = '✅ 已复制';
          setTimeout(() => btn.textContent = original, 1500);
        }).catch(() => {
          alert('复制失败,请手动复制');
        });
      });
    });

    document.body.appendChild(modal);
  };

  // ======================
  // 按钮事件绑定
  // ======================
  root.querySelectorAll('.auth-dock-btn').forEach(btn => {
    btn.addEventListener('click', () => {
      const action = btn.dataset.action;

      if (action === 'capture') {
        const currentUrl = window.location.href;
        const verificationUrl = getVerificationUrl(siteKey);

        if (!isOnVerificationPage(siteKey, currentUrl)) {
          if (verificationUrl) {
            const confirmed = confirm(
              `⚠️ 为确保授权有效,请先跳转到 ${SITE_NAMES[siteKey]} 的个人页面。\n是否立即跳转?`
            );
            if (confirmed) {
              window.location.href = verificationUrl;
            }
          } else {
            alert('❌ 未配置该站点的验证页面');
          }
          return;
        }

        btn.disabled = true;
        btn.textContent = '捕获中…';

        chrome.runtime.sendMessage(
          { action: 'captureCookiesFromOverlay', siteKey },
          (response) => {
            if (response?.success) {
              btn.textContent = '✅ 成功';
              setTimeout(closeCurrentTab, 1500);
            } else {
              btn.textContent = '❌ 失败';
              setTimeout(() => {
                btn.disabled = false;
                btn.textContent = '✅ 授权';
              }, 2000);
            }
          }
        );
      } else if (action === 'account') {
        showAccountModal();
      } else if (action === 'autologin') {
        const strategy = autoLoginStrategies[siteKey];
        if (!strategy) {
          alert(`⚠️ ${SITE_NAMES[siteKey]} 暂不支持自动登录`);
          return;
        }

        chrome.runtime.sendMessage({ action: 'getAccountInfo', siteKey }, (response) => {
          if (!response?.success) {
            alert('❌ 未配置账号信息');
            return;
          }

          try {
            strategy(response.account);
            // alert(`✅ ${SITE_NAMES[siteKey]} 账号已填充,1.5秒后自动登录...`);
          } catch (err) {
            alert(`❌ 自动登录失败:${err.message}`);
          }
        });
      } else if (action === 'close') {
        closeCurrentTab();
      }
    });
  });
})();

3、manifest.json

{
  "manifest_version": 3,
  "name": "Auth Overlay",
  "version": "1.0",
  "description": "Show auth overlay on target sites.",

  "host_permissions": [
    "https://*.zhihu.com/*",
    "https://*.163.com/*",
    "https://*.baidu.com/*"
  ],

  "background": {
    "service_worker": "background.js"
  },

  "content_scripts": [
    {
      "matches": [
        "https://*.zhihu.com/*",
        "https://*.163.com/*",
        "https://*.baidu.com/*"
      ],
      "js": ["content.js"],
      "run_at": "document_idle"
    }
  ],

  "permissions": ["cookies", "tabs"]
}

springboot 添加获取账号api接口

package press.huang.demo;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/api/auth")
public class AuthAccountController {

    private static final Map<String, Account> MOCK_ACCOUNTS = new HashMap<>();

    static {
        MOCK_ACCOUNTS.put("zhihu", new Account("zhihu_user@zhihu.com", "zhihupassword"));
        MOCK_ACCOUNTS.put("netease", new Account("netease_user@163.com", "NeteasePass456!"));
        MOCK_ACCOUNTS.put("baidu", new Account("baidu_user@baidu.com", "baidupassword"));
    }

    @GetMapping("/account/{siteKey}")
    public ResponseEntity<Map<String, Object>> getAccountBySite(@PathVariable String siteKey) {
        Account account = MOCK_ACCOUNTS.get(siteKey);

        Map<String, Object> response = new HashMap<>();

        if (account == null) {
            response.put("success", false);
            response.put("error", "Unsupported site: " + siteKey);
            return ResponseEntity.badRequest().body(response);
        }

        response.put("success", true);
        response.put("account", account); // Jackson 会自动序列化为 { "username": "...", "password": "..." }
        return ResponseEntity.ok(response);
    }

    // 内部 DTO 类(JDK 8 兼容)
    private static class Account {
        private final String username;
        private final String password;

        public Account(String username, String password) {
            this.username = username;
            this.password = password;
        }

        // ✅ 提供 getter(Jackson 默认使用 getter 序列化)
        public String getUsername() {
            return username;
        }

        public String getPassword() {
            return password;
        }
    }
}