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