代表性面试主题

前端面试:如何设计浏览器扩展的最小权限与渐进授权?

前端困难
Offer.cc 编辑团队发布 更新

题干

你要做一个浏览器扩展:用户点击工具栏按钮后读取当前页面并调用一个受控 API。请设计 Manifest V3 权限方案,说明何时使用 activeTab、optional permissions 和 host_permissions,如何处理用户拒绝、权限撤销、版本升级及安全审计。

题干与适用场景

扩展只在用户主动点击工具栏按钮后处理当前页面,随后把经过筛选的字段发送到受控后端。它不需要后台扫描所有标签页,也不需要读取任意站点的 Cookie。请给出 Manifest V3 的权限拆分、运行时流程、失败降级和上线验证方案。

题目考察浏览器扩展的权限边界与产品化安全。服务端仍需做身份认证、数据最小化和审计;扩展权限不能替代这些控制。

面试官考察点

面试官会看你是否把“能调用 API”“能访问哪些主机”和“用户何时同意”分开。Chrome 文档将 API 权限、主机权限、可选权限和 activeTab 分别处理;权限会影响可访问的 API、URL、安装警告和升级行为。

高质量回答从最小能力开始,解释为什么一次性当前页操作不应默认申请 <all_urls>,并覆盖拒绝、撤销、升级和遥测。只说“加 permissions”而不讲权限生命周期,无法证明安全边界。

回答前需要澄清的问题

  • “当前页面”是否包含特殊页面、跨域 iframe、文件 URL 或隐身窗口?
  • API 是否必须读取完整正文,还是只需用户选中的字段?
  • 是否需要在用户未点击时持续监听页面变化?
  • 支持 Chrome、Firefox 还是多个 WebExtensions 实现?
  • 企业版是否会通过策略预配置权限,且审计字段有什么保留期限?

30 秒回答框架

“我先把能力拆成当前页读取、后端通信和存储。当前页一次性操作优先用 activeTab,不申请全站主机权限;只有确实需要跨页面持续工作时才把精确主机模式放入 optional_host_permissions,在用户触发功能时申请。API 权限只声明必需项,拒绝或撤销就停止处理并提供手动复制降级。升级前比较权限差异,监控新增权限和数据流,回滚时撤销新能力。”

分步骤深入解答

第一步:把功能映射到权限

storagescripting 等 API 权限解决扩展 API 能力;主机权限决定可交互的 URL 范围。权限列表不应按“以后可能用到”添加,而应按每条数据流写出最小范围。

一个只处理用户点击所在标签页的起点可以是:

json
{
  "manifest_version": 3,
  "permissions": ["activeTab", "scripting", "storage"],
  "optional_host_permissions": ["https://app.example.com/*"]
}

若只在当前页注入脚本,activeTab 在用户动作后提供临时访问;它会随标签页关闭或导航而失效。若产品需要后台持续读取 app.example.com,再评估精确的可选主机权限,不把 *://*/* 当作默认方案。

第二步:设计渐进授权流程

安装时只请求核心、低风险能力。用户点击“分析此页”后,先确认当前 URL 是否在允许范围;首次需要额外主机时调用 chrome.permissions.request(),在界面解释用途、范围和退出方式。

ts
const granted = await chrome.permissions.request({
  origins: ["https://app.example.com/*"]
});
if (!granted) {
  showManualCopyFallback();
  return;
}

申请成功只代表浏览器能力可用,不能跳过服务端认证。申请失败、用户稍后撤销或策略禁用时,代码应回到无权限状态,不反复弹窗骚扰用户。

第三步:区分 active、granted 与可撤销状态

Chromium 文档区分当前生效权限与历史授予权限。chrome.permissions.remove() 会降低当前能力,但历史授予集合可能仍记录该权限;再次申请时未必再次弹窗。因此产品应把“当前可用”作为运行判断,并在设置页提供明确的撤销入口。

每次任务开始前调用 chrome.permissions.contains() 检查实际权限;任务中监听 permissions.onRemoved,立即停止读取和上传,清除待发送队列,并记录可审计的权限变化事件。

第四步:处理拒绝、升级和回滚

权限拒绝时显示不依赖技术术语的下一步:改用手动选择、复制粘贴或退出。不要把拒绝当成网络错误,也不要在后台自动重试申请。

版本升级前生成权限差异清单:新增 API、主机模式、内容脚本匹配范围和数据字段分别审批。Chrome 在权限提升时可能停用扩展并等待用户重新同意;删除权限也不等于历史授予集合被清空,所以回滚测试必须覆盖“旧版本曾获批”和“从未获批”两类用户。

第五步:把权限做成可验证的安全边界

内容脚本暴露在网页输入影响下,消息处理要做来源、消息类型和数据长度校验;服务端对扩展身份、用户身份、目标资源和字段白名单再次授权。主机权限只限制浏览器能力,不保证网页本身可信,也不阻止扩展把已读数据上传到错误的后端。

上线前建立矩阵:首次安装、允许与拒绝、撤销、导航后 activeTab 失效、可选主机申请、升级增加权限、回滚、企业策略禁用、浏览器不支持和离线。遥测只记录权限键、主机模式、结果和版本,不记录页面正文。

高质量示范回答

“我会先画出能力和数据流。工具栏点击后只处理当前页,所以核心方案使用 activeTabscripting 和必要的 storage,不申请全站访问。只有后台持续处理 app.example.com 才把精确主机模式放到可选权限,并在用户触发该功能时申请。申请前说明用途和范围,拒绝后提供手动选择,不循环弹窗。

每次任务前检查当前权限,监听撤销事件;权限失效就停止读取、清理待发送队列并让服务端重新校验身份和资源。升级前比较 API、主机和内容脚本差异,验证新增权限导致的停用、重新同意和回滚路径。最后用权限矩阵和匿名化遥测验证:没有后台扫描、没有跨主机注入、没有页面正文进入日志,且拒绝与撤销都能安全降级。”

常见错误

  • 默认申请 <all_urls> 扩大读取和注入面并增加用户警告 → 先用 activeTab,持续场景再精确申请主机。
  • optional_host_permissions 当成自动授权 → 声明不等于用户同意 → 在功能触发时申请并处理拒绝。
  • 只检查安装时权限 → 用户可在之后撤销,当前能力会变化 → 任务前检查并监听移除事件。
  • 升级只看代码差异 → 新权限可能导致扩展被停用 → 比较权限集合并测试旧授予状态。
  • 把浏览器权限当成服务端授权 → 扩展仍可能把数据发错账户 → 服务端重做身份、资源和字段校验。
  • 权限申请里塞满技术名词 → 用户难以形成正确的风险判断 → 用用途、范围和退出方式解释。

追问及应对

追问一:为什么不直接申请 <all_urls>

当前页一次性操作不需要后台访问所有站点。activeTab 在用户动作后提供临时能力,降低长期暴露面;只有明确的持续跨页面需求才值得评估精确主机范围。

追问二:用户撤销权限后,已经上传的数据怎么办?

撤销只能阻止后续浏览器访问,不能追回已离开设备的数据。服务端应按最小字段、短留存和可删除策略设计;扩展立即清理本地待发送队列,并向用户说明已完成的处理范围。

追问三:可选权限申请后为什么仍要做后端校验?

浏览器权限只回答扩展能否读取某个页面,不回答用户是否有权访问业务资源。后端必须校验会话、扩展版本、目标资源和字段白名单,并拒绝过期或异常请求。

追问四:Chrome 和 Firefox 的权限行为能否直接假设一致?

不能。两者共享 WebExtensions 概念,但提示文案、匹配模式和运行时边界可能不同。应针对目标浏览器实测安装、申请、撤销、导航和升级,并把差异写入兼容性矩阵。

公开来源

同类题目