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

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

第 10 章:权限管理(Permissions)

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


10.1 权限分类

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

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

权限与用户信任

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

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

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

10.2 常用权限一览

权限名称API说明影响等级
activeTabtabs访问当前活动标签页
tabstabs访问所有标签页信息
storagestorage读写扩展存储
contextMenuscontextMenus创建右键菜单
notificationsnotifications显示桌面通知
alarmsalarms创建定时器
bookmarksbookmarks读写书签
historyhistory读取浏览历史
cookiescookies读写 Cookie
scriptingscripting程序化注入脚本
sidePanelsidePanel使用侧边栏
webRequestwebRequest观察网络请求
declarativeNetRequestdeclarativeNetRequest拦截/修改请求
downloadsdownloads管理下载
identityidentityGoogle 账号认证
nativeMessagingnativeMessaging与本地应用通信
debuggerdebugger调试协议极高
managementmanagement管理其他扩展

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 扩展阅读