强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Chrome 扩展开发完全指南 / 第 10 章:权限管理(Permissions)

第 10 章:权限管理(Permissions)

权限系统是 Chrome 扩展安全模型的核心。合理声明权限既能保证功能正常,又能赢得用户信任。过度申请权限会导致用户拒绝安装,权限不足则功能受限。本章将全面讲解权限的声明、申请和最佳实践。


10.1 权限分类

Chrome 扩展的权限分为三大类:

分类 声明字段 申请时机 影响
普通权限 permissions 安装时 安装时显示给用户
可选权限 optional_permissions 运行时按需申请 用户需确认授予
主机权限 host_permissions 安装时或运行时 控制访问特定网站

权限与用户信任

权限数量与安装率的关系:

权限少    ████████████████████████  安装率高
权限适中  ████████████████          安装率中等
权限多    ████████                  安装率低

📌 最佳实践:只申请必需的权限,其余使用可选权限

10.2 常用权限一览

权限名称 API 说明 影响等级
activeTab tabs 访问当前活动标签页
tabs tabs 访问所有标签页信息
storage storage 读写扩展存储
contextMenus contextMenus 创建右键菜单
notifications notifications 显示桌面通知
alarms alarms 创建定时器
bookmarks bookmarks 读写书签
history history 读取浏览历史
cookies cookies 读写 Cookie
scripting scripting 程序化注入脚本
sidePanel sidePanel 使用侧边栏
webRequest webRequest 观察网络请求
declarativeNetRequest declarativeNetRequest 拦截/修改请求
downloads downloads 管理下载
identity identity Google 账号认证
nativeMessaging nativeMessaging 与本地应用通信
debugger debugger 调试协议 极高
management management 管理其他扩展

10.3 声明权限

manifest.json 中的三种声明方式

{
  "permissions": [
    "storage",
    "contextMenus",
    "alarms",
    "sidePanel",
    "activeTab",
    "scripting"
  ],

  "optional_permissions": [
    "tabs",
    "bookmarks",
    "history",
    "cookies",
    "downloads",
    "notifications"
  ],

  "host_permissions": [
    "*://*.example.com/*",
    "*://api.example.com/*"
  ],

  "optional_host_permissions": [
    "*://*/*"
  ]
}

权限与 API 的对应关系

┌───────────────────────────────────────────────────┐
│                   manifest.json                     │
│                                                     │
│  permissions          optional_permissions          │
│  ┌──────────┐        ┌──────────────┐              │
│  │ storage   │        │ tabs         │              │
│  │ context-  │        │ bookmarks    │              │
│  │ Menus     │        │ history      │              │
│  │ alarms    │        │ cookies      │              │
│  └──────────┘        └──────────────┘              │
│       │                     │                       │
│       ▼                     ▼                       │
│  安装时全部授予        运行时按需申请                  │
│  ✅ 立即可用           ⚠️ 需要用户确认               │
│                                                     │
│  host_permissions     optional_host_permissions     │
│  ┌──────────────┐    ┌──────────────┐              │
│  │*://*.ex.com/*│    │*://*/*       │              │
│  └──────────────┘    └──────────────┘              │
│       │                     │                       │
│       ▼                     ▼                       │
│  安装时授予            运行时按需申请                  │
└───────────────────────────────────────────────────┘

10.4 activeTab 权限

activeTab 是最安全也最推荐的权限之一。它只在用户主动触发(点击扩展图标、快捷键等)时临时授权。

特点

特性 说明
触发条件 用户点击扩展图标、右键菜单、快捷键
有效时间 仅在触发后持续,用户离开后失效
访问范围 仅限当前活动标签页
不需要 host_permissions ✅ 可替代广泛的主机权限

使用示例

// 用户点击扩展图标时,获得 activeTab 权限
chrome.action.onClicked.addListener(async (tab) => {
  // 此时可以注入脚本到当前标签页
  await chrome.scripting.executeScript({
    target: { tabId: tab.id },
    func: () => {
      document.body.style.border = '3px solid red';
    }
  });

  // 可以获取标签页的详细信息
  console.log('URL:', tab.url);
  console.log('Title:', tab.title);
});

📌 最佳实践:如果扩展只需要在用户主动操作时访问标签页,使用 activeTab + scripting 而非 tabs + host_permissions


10.5 可选权限

10.5.1 运行时申请权限

// 请求可选权限
async function requestPermission(permission) {
  try {
    const granted = await chrome.permissions.request({
      permissions: [permission]
    });

    if (granted) {
      console.log(`权限 ${permission} 已授予`);
      return true;
    } else {
      console.log(`权限 ${permission} 被拒绝`);
      return false;
    }
  } catch (error) {
    console.error('申请权限失败:', error);
    return false;
  }
}

// 请求主机权限
async function requestHostPermission(hostPattern) {
  try {
    const granted = await chrome.permissions.request({
      origins: [hostPattern]
    });
    return granted;
  } catch (error) {
    console.error('申请主机权限失败:', error);
    return false;
  }
}

10.5.2 检查权限

// 检查是否拥有某个权限
async function hasPermission(permission) {
  return await chrome.permissions.contains({
    permissions: [permission]
  });
}

// 检查主机权限
async function hasHostPermission(hostPattern) {
  return await chrome.permissions.contains({
    origins: [hostPattern]
  });
}

// 使用示例
async function showBookmarks() {
  const hasBookmarks = await hasPermission('bookmarks');

  if (!hasBookmarks) {
    const granted = await requestPermission('bookmarks');
    if (!granted) {
      showToast('需要书签权限才能使用此功能');
      return;
    }
  }

  // 现在可以安全使用 bookmarks API
  const bookmarks = await chrome.bookmarks.getRecent(10);
  renderBookmarks(bookmarks);
}

10.5.3 UI 中的权限提示

// 在 Options 页面中展示权限状态
class PermissionsPanel {
  constructor(container) {
    this.container = container;
    this.permissions = [
      { name: 'bookmarks', label: '书签', desc: '读取和管理书签' },
      { name: 'history', label: '历史记录', desc: '读取浏览历史' },
      { name: 'tabs', label: '标签页', desc: '访问标签页信息' },
      { name: 'cookies', label: 'Cookie', desc: '读写网站 Cookie' },
      { name: 'notifications', label: '通知', desc: '显示桌面通知' }
    ];
  }

  async render() {
    const html = [];

    for (const perm of this.permissions) {
      const has = await chrome.permissions.contains({
        permissions: [perm.name]
      });

      html.push(`
        <div class="permission-item">
          <div class="permission-info">
            <span class="permission-label">${perm.label}</span>
            <span class="permission-desc">${perm.desc}</span>
          </div>
          <label class="toggle-switch">
            <input type="checkbox" data-permission="${perm.name}"
                   ${has ? 'checked' : ''}>
            <span class="slider"></span>
          </label>
        </div>
      `);
    }

    this.container.innerHTML = html.join('');

    // 绑定切换事件
    this.container.querySelectorAll('input[type="checkbox"]')
      .forEach(input => {
      input.addEventListener('change', (e) => {
        this.togglePermission(e.target.dataset.permission, e.target.checked);
      });
    });
  }

  async togglePermission(permission, enable) {
    if (enable) {
      const granted = await chrome.permissions.request({
        permissions: [permission]
      });
      if (!granted) {
        // 用户拒绝,恢复开关状态
        this.render();
      }
    } else {
      await chrome.permissions.remove({
        permissions: [permission]
      });
    }
  }
}

10.6 权限与功能的对应

按需申请模式

// lib/feature-gates.js

class FeatureGate {
  static features = {
    autoLogin: {
      requiredPermissions: ['cookies'],
      requiredHosts: ['*://accounts.google.com/*'],
      description: '自动登录功能'
    },
    historySearch: {
      requiredPermissions: ['history'],
      description: '历史记录搜索'
    },
    bookmarkSync: {
      requiredPermissions: ['bookmarks'],
      description: '书签同步'
    },
    downloadManager: {
      requiredPermissions: ['downloads'],
      description: '下载管理'
    }
  };

  static async check(featureName) {
    const feature = this.features[featureName];
    if (!feature) throw new Error(`Unknown feature: ${featureName}`);

    const required = {};
    if (feature.requiredPermissions) {
      required.permissions = feature.requiredPermissions;
    }
    if (feature.requiredHosts) {
      required.origins = feature.requiredHosts;
    }

    return await chrome.permissions.contains(required);
  }

  static async ensure(featureName) {
    const has = await this.check(featureName);
    if (has) return true;

    const feature = this.features[featureName];
    const request = {};
    if (feature.requiredPermissions) {
      request.permissions = feature.requiredPermissions;
    }
    if (feature.requiredHosts) {
      request.origins = feature.requiredHosts;
    }

    try {
      return await chrome.permissions.request(request);
    } catch {
      return false;
    }
  }
}

// 使用
if (await FeatureGate.ensure('historySearch')) {
  const results = await chrome.history.search({ text: query });
  renderHistory(results);
}

10.7 权限变更监听

// 监听权限授予
chrome.permissions.onAdded.addListener((permissions) => {
  console.log('新增权限:', permissions);
  // permissions.permissions — 新增的 API 权限列表
  // permissions.origins — 新增的主机权限列表
});

// 监听权限移除
chrome.permissions.onRemoved.addListener((permissions) => {
  console.log('移除权限:', permissions);
  // 禁用对应功能
  disableFeaturesRequiring(permissions);
});

function disableFeaturesRequiring(permissions) {
  if (permissions.permissions?.includes('bookmarks')) {
    document.getElementById('bookmarkFeature')?.classList.add('disabled');
  }
  if (permissions.permissions?.includes('history')) {
    document.getElementById('historyFeature')?.classList.add('disabled');
  }
}

10.8 常见权限模式

模式对比

模式 权限声明 用户感知 适用场景
最小权限 storage, activeTab “权限很少” 简单工具类扩展
按需申请 最小 + 可选权限 “需要时才申请” 多功能扩展
全部预申请 所有权限都在 permissions “权限较多” 专业工具

各场景推荐权限

// 场景:网页剪藏工具
{
  "permissions": ["storage", "activeTab", "contextMenus", "scripting"],
  "optional_permissions": ["tabs", "downloads"],
  "optional_host_permissions": ["<all_urls>"]
}

// 场景:密码管理器
{
  "permissions": ["storage", "tabs", "activeTab", "scripting", "notifications"],
  "host_permissions": ["<all_urls>"],
  "optional_permissions": ["cookies", "webRequest"]
}

// 场景:广告拦截器
{
  "permissions": [
    "storage", "declarativeNetRequest",
    "declarativeNetRequestWithHostAccess"
  ],
  "host_permissions": ["<all_urls>"]
}

10.9 注意事项

问题 说明 解决方案
安装率低 权限太多吓跑用户 使用可选权限,按需申请
功能不工作 忘记声明必需权限 检查 API 文档中的权限要求
Chrome Web Store 审核被拒 权限过度 只申请实际使用的权限
activeTab 失效 用户离开标签页 再次触发时重新获取
可选权限被拒绝 用户选择"拒绝" 功能降级,提示用户

10.10 扩展阅读