文章
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 || '未知错误');
}
}
);
});
});- 打开 Chrome → 地址栏输入:
chrome://extensions/ - 开启右上角「开发者模式」
- 点击「加载已解压的扩展程序」 → 选择本项目文件夹
- 手动访问
https://zhihu.com→ 完成登录 - 点击浏览器右上角扩展图标 → 点击 “捕获知乎 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;
}
}
}