从 0 到上线:我如何用开源打造一款密码管理 Chrome 插件

从 0 到上线:我如何用开源制打造一款密码管理 Chrome 插件

当 Google 密码管理器对内部系统无能为力时,我决定自己造一个轮子

前言

作为一个每天要登录十几个不同系统的开发者,我一直依赖 Chrome 自带的密码管理功能。它很方便,能自动记住密码、跨设备同步,直到我遇到了公司内部系统。

那些运行在 10.xxx.xxx.xxx:8080 上的老旧管理后台、内部 Governance 平台、BPM 工作流引擎......Chrome 要么压根不弹出保存密码的提示,要么保存了也无法自动填充。更糟的是,这些系统的密码还必须每 90 天更换一次,手动输入成了每天的痛苦。

我问自己:能不能做一个完全本地化、能识别任何表单、还能跨设备安全同步的密码管理器?

于是,这个开源项目诞生了。


一、项目定位与核心功能

我需要的是一个不依赖任何云服务、数据完全掌握在自己手里、且能暴力识别所有登录表单的 Chrome 扩展。

最终成品包含以下核心能力:

功能 描述
🔐 主密码加密 所有密码使用用户主密码 AES 类加密(示例实现),离开主密码无法解密
🧠 智能表单识别 通过多种 CSS 选择器暴力匹配用户名/密码输入框,支持独立输入框
⚡ 一键填充 在当前网站列出所有匹配账号,支持多选填充
💾 本地存储 数据仅存在 chrome.storage.local,不上传任何服务器
⏱️ 定时备份 按小时/天/周自动导出加密数据到下载文件夹
🔍 密码库搜索 实时过滤网站/用户名/备注

技术栈:Chrome Extension Manifest V3 + 原生 JavaScript + Chrome Storage API + Alarms API + Downloads API


二、痛点驱动:为什么 Google 记不住内部系统?

Chrome 的密码管理器依赖标准的 <form> 结构和特定的 input 属性(如 autocomplete="username")。但企业内部系统往往:

  • 使用非标准表单(甚至没有 <form> 包裹)
  • 字段命名随意(logonidemployeeNoj_username
  • 采用框架动态生成的输入框(React/Vue 异步渲染)

我的插件必须能"强行"识别这些野路子表单

解决方案:暴力选择器 + DOM 监听

content.js 中,我定义了一组"万能选择器":

javascript 复制代码
const usernameSelectors = [
  'input[type="text"]',
  'input[type="email"]',
  'input[name*="user"]',
  'input[name*="email"]',
  'input[placeholder*="用户名" i]',
  'input[autocomplete="username"]',
  // ... 共十几种
];

遍历页面所有 <form> 以及独立输入框,只要同时匹配到用户名字段和密码字段,就判定为登录表单。同时利用 MutationObserver 监听 DOM 变化,单页应用动态添加的表单也能实时捕获。


三、架构设计

3.1 扩展的四个主要部分

bash 复制代码
password-manager-extension/
├── manifest.json          # 扩展配置(权限、脚本注入)
├── popup.html/js          # 弹出窗口 UI,用户主要交互界面
├── background.js          # Service Worker,管理定时器、消息路由
├── content.js             # 注入到网页的内容脚本,负责表单检测和填充
├── options.html/js        # 设置页面(主密码、备份、自动开关)

3.2 数据流

  1. 保存密码 :用户在 popup 手动添加或通过 content 检测表单保存 → popup 调用 chrome.storage.local.set 存储加密后的数据。
  2. 填充密码:用户点击"填充" → popup 向当前 tab 的 content script 发送消息(包含解密后的用户名/密码)→ content script 操作真实 DOM 输入值并触发事件。
  3. 自动备份 :background 中设定 chrome.alarms 定时器 → 到期后读取存储数据 → 生成 JSON 文件 → 调用 chrome.downloads.download 保存到本地。

3.3 安全性考虑

  • 主密码从不保存明文 :用户设置主密码时,我们存储的是 simpleHash(masterPassword),加密时用该哈希值作为密钥。
  • 加密算法:示例中使用 Base64 + 反转字符串(生产环境务必替换为 Web Crypto API 的 AES-GCM)。
  • 备份文件:仅包含加密后的密码数据,不包含主密码。即使文件泄露,没有主密码也无法解密。

四、关键实现细节

4.1 表单检测的核心算法

content.js 中的 detectLoginForms(returnDOMElements) 函数:

javascript 复制代码
function detectLoginForms(returnDOMElements) {
  const detectedForms = [];
  const forms = document.querySelectorAll('form');
  
  forms.forEach((form) => {
    const usernameInputs = findUsernameInputs(form, returnDOMElements);
    const passwordInputs = findPasswordInputs(form, returnDOMElements);
    
    if (usernameInputs.length && passwordInputs.length) {
      detectedForms.push({
        website: document.title,
        url: location.href,
        usernameInputs,
        passwordInputs,
      });
    }
  });
  
  // 若没有找到 form 内的,再查找独立输入框...
  return detectedForms;
}

findUsernameInputs 内部遍历上述选择器数组,收集所有匹配的 DOM 元素(或序列化后的信息,取决于 returnDOMElements 标志)。

这个标志很关键:用于 UI 展示时,我们只需要字段元数据;用于实际填充时,我们必须拿到真实 DOM 元素才能赋值

4.2 自动填充如何"骗"过现代前端框架?

很多 React/Vue 组件监听 inputchange 事件,直接修改 input.value 不会触发框架更新。因此填充时需要手动 dispatch 事件:

javascript 复制代码
usernameInput.value = credential.username;
usernameInput.dispatchEvent(new Event('focus', { bubbles: true }));
usernameInput.dispatchEvent(new Event('input', { bubbles: true }));
usernameInput.dispatchEvent(new Event('change', { bubbles: true }));
usernameInput.dispatchEvent(new Event('blur', { bubbles: true }));

这四种事件基本覆盖了绝大多数框架的响应机制。

4.3 定时备份的实现(background.js)

javascript 复制代码
chrome.alarms.create('autoBackup', {
  periodInMinutes: intervalMap[backupInterval],
  delayInMinutes: 1
});

chrome.alarms.onAlarm.addListener((alarm) => {
  if (alarm.name === 'autoBackup') performBackup();
});

function performBackup() {
  chrome.storage.local.get(['passwords'], (result) => {
    const backupData = {
      passwords: result.passwords,
      exportedAt: new Date().toISOString(),
      version: '1.0'
    };
    const blob = new Blob([JSON.stringify(backupData, null, 2)]);
    const url = URL.createObjectURL(blob);
    chrome.downloads.download({
      url: url,
      filename: `password_backup_${timestamp}.json`,
      saveAs: false
    });
  });
}

注意:需要 "permissions": ["alarms", "downloads"]


五、那些踩过的坑与解决方案

坑 1:内容脚本与弹出窗口通信时,dispatchEvent is not a function

原因 :在 detectForms 返回给 popup 时,我序列化了 DOM 元素为普通对象,导致 popup 中拿到的是 {name, type, id...} 而不是真实元素。然后 popup 直接对这些对象调用 .dispatchEvent,自然报错。

解决 :增加 returnDOMElements 参数。在填充流程中,content script 自己重新调用 detectLoginForms(true) 获取真实 DOM 元素,而不是依赖 popup 传过来的序列化数据。

坑 2:某些网站的表单是动态加载的(如单页应用)

解决 :使用 MutationObserver 监听 document.body 的子节点变化,当新增节点时重新执行一次表单检测。

坑 3:定时备份不触发

原因 :Service Worker 可能被浏览器挂起,chrome.alarms 虽能唤醒,但需确保在 chrome.runtime.onInstalled 中注册,并在每次设置变化时重新创建 alarm。

解决 :在 background.js 中监听 onInstalled 和来自 options 页面的 updateBackupSchedule 消息,每次先 clearcreate

坑 4:密码列表长 URL 导致界面溢出

解决 :CSS 中添加 word-break: break-all,同时为长文本添加 title 属性,悬停显示完整内容。


六、如何安装与使用?

开发者模式安装

  1. 下载源码文件夹(包含 manifest.json 等)。
  2. GitHub 访问 https://github.com/qingjie-li/password-manager下载本地 。
  3. Google设置 → 管理扩展程序 → 开启"开发者模式" → 点击"加载已解压的扩展程序" → 选择文件夹。

快速上手流程

  1. 设置主密码:打开扩展弹出窗口 → 设置标签 → 输入主密码。
  2. 添加密码:访问任意登录页 → 自动识别标签 → 开始检测 → 保存检测到的凭据。
  3. 填充密码:再次访问该网站 → 当前网站标签 → 选择账号 → 点击填充。

七、开源与未来计划

项目完全开源,代码可在此仓库找到(链接略)。欢迎 PR。

后续计划:

  • 使用 Web Crypto API 替换当前简易加密
  • 支持通过 WebDAV/坚果云 跨设备同步加密数据(可选)
  • 增加导入 Chrome 原生密码 CSV 的功能

八、总结

做这个插件的最大感受是:"痛点是最好的产品经理"。当 Google 密码管理器无法满足我那些"野路子"内部系统时,自己动手造一个反而更高效。

如果你也受困于公司内部系统的密码管理,不妨试试这个插件,或者基于这个思路自己定制。代码是开源的,欢迎一起完善。


本文首发于掘金,作者:一杯猫 如果觉得有用,请点个赞 ❤️,让更多被内部系统折磨的开发者看到。

相关推荐
Cdlblbq2 小时前
搜索会员中心 创作中心Vue2项目一键打包成桌面应用
前端·javascript·vue.js·electron
A_nanda3 小时前
vue实现后端传输逐帧图像数据
前端·javascript·vue.js
qq_12084093713 小时前
Three.js 工程向:动画循环与时间步进稳定性实践
前端·javascript
旷世奇才李先生3 小时前
React18\+TypeScript实战: Hooks封装与企业级组件开发
前端·javascript·typescript
午安~婉3 小时前
Electron(续4)利用AI辅助完成配置功能
前端·javascript·electron·应用打包与发布
helloweilei4 小时前
Web Streams 简介
前端·javascript
一川_4 小时前
前端驱动工业报警:基于 WebSocket 与网关的三色蜂鸣灯实时报警系统实战
javascript·websocket
狗都不学爬虫_4 小时前
小程序逆向 - Hai尔(AliV3拖动物品)
javascript·爬虫·python·网络爬虫
We་ct4 小时前
HTML5 原生拖拽 API 基础原理与核心机制
前端·javascript·html·api·html5·浏览器·拖拽