Feature-Policy实战指南:提升前端安全与性能的关键策略
我的写法,亲测靠谱
我在项目里用 Feature-Policy(现在叫 Permissions-Policy 了,但很多人还是习惯叫老名字)已经好几年了。说实话,一开始根本没搞懂这玩意儿到底有啥用,直到某天被安全团队扫出来一堆“高危漏洞”——比如页面能调用摄像头、麦克风、甚至 geolocation,而我们压根没用到这些功能。那一刻我才意识到,关掉不用的权限,不是“可选项”,而是“必须做”。
我现在的做法很直接:在 HTTP 响应头里加一个保守但实用的策略。核心原则就一条:只开你真正需要的,其他一律关掉。下面是我现在项目里用的配置:
Feature-Policy:
accelerometer 'none';
autoplay 'self';
camera 'none';
display-capture 'none';
document-domain 'none';
encrypted-media 'self';
fullscreen 'self';
geolocation 'none';
gyroscope 'none';
magnetometer 'none';
microphone 'none';
midi 'none';
payment 'none';
picture-in-picture 'self';
sync-xhr 'self';
usb 'none';
vr 'none';
wake-lock 'none';
xr-spatial-tracking 'none'
解释一下几个关键点:
'none'表示完全禁止,比如摄像头、麦克风、地理位置这些敏感功能,我们前端根本没用,那就直接关死。'self'表示只允许同源使用,比如autoplay(自动播放视频),我们自己的 CDN 视频需要自动播放,但第三方 iframe 里的不能播,所以设为'self'。sync-xhr我也限制为'self',因为同步 XHR 会阻塞主线程,虽然我们基本不用,但万一有遗留代码,至少别让第三方脚本搞出性能问题。
这个配置上线后,安全扫描直接从“高危”降到“低危”,而且没引发任何功能异常。亲测有效。
这几种错误写法,别再踩坑了
我见过太多人把 Feature-Policy 写成“形式主义”。这里列几个典型反面案例,都是我或同事踩过的坑:
错误1:全开,等于没开
Feature-Policy: *
或者更隐蔽一点:
Feature-Policy: camera *; microphone *; geolocation *
这种写法看似“兼容性好”,实则把安全策略完全废掉了。你开了个头,攻击者就能通过 iframe 或嵌入脚本调用这些敏感 API。千万别图省事这么干。
错误2:拼写错误或大小写混用
比如写成 Camera 'none'(首字母大写)或者 camara 'none'(拼错)。浏览器会直接忽略整条指令,导致策略失效。我曾经因为一个拼写错误,折腾了半天才发现安全扫描还是报高危,最后靠 Chrome DevTools 的 Network 面板看响应头才定位到问题。
错误3:用 meta 标签代替 HTTP 头
<meta http-equiv="Feature-Policy" content="camera 'none'">
很多教程会教你在 HTML 里加这个 meta 标签,但它不支持所有指令!比如 sync-xhr、document-domain 这些只能通过 HTTP 头设置。而且,meta 标签对 iframe 的控制力也弱。我建议:除非你完全控制不了服务器(比如纯静态托管),否则一律用 HTTP 头。
错误4:只关一部分,漏掉关键项
比如只关了 camera 和 microphone,但忘了 geolocation 或 usb。现代浏览器支持的权限越来越多,像 payment、vr 这些冷门但危险的权限,也得一并关掉。我现在的做法是:先查 MDN 上的最新支持列表,然后按“最小权限”原则逐个关闭。
实际项目中的坑
Feature-Policy 看似简单,但真上生产环境,还是会遇到各种意外。
第一个坑是第三方 SDK。比如我们接入了一个数据分析工具,它内部用了 sync-xhr(虽然很蠢,但确实存在)。我一开始把 sync-xhr 设为 'none',结果那个 SDK 直接挂了。后来改成 'self' 才解决。所以,加策略前,一定要梳理清楚所有第三方依赖。可以用 Chrome DevTools 的 Console 看有没有报 NotAllowedError,这是 Feature-Policy 拦截的典型信号。
第二个坑是开发环境和生产环境不一致。本地开发时,因为没配响应头,一切正常;一上线,某些功能突然失效。我现在会在本地 nginx 里也加上同样的策略,提前暴露问题。如果你用的是 Express,可以这样加中间件:
app.use((req, res, next) => {
res.setHeader('Feature-Policy',
accelerometer 'none';
autoplay 'self';
camera 'none';
// ... 其他策略
.replace(/s+/g, ' ').trim());
next();
});
第三个坑是浏览器兼容性。虽然主流浏览器都支持了,但 Safari 对某些指令的支持比较晚。比如 display-capture 在旧版 Safari 会报语法错误,导致整个策略被忽略。我的应对策略是:先用 feature-policy npm 包生成兼容性较好的字符串,或者干脆在 Nginx 层做 UA 判断,只对现代浏览器发送完整策略。
另外,记得 Feature-Policy 已经被 Permissions-Policy 取代了,新项目建议直接用新语法。不过目前两个头可以共存,老浏览器认旧的,新浏览器认新的。我的做法是同时发两个头:
Feature-Policy: camera 'none'; microphone 'none'
Permissions-Policy: camera=(), microphone=()
这样过渡最稳妥。
结尾提醒
以上是我总结的最佳实践,核心就是:保守配置、严格测试、持续监控。改完后可能还会有一两个小问题(比如某个第三方脚本报错),但只要不影响核心功能,安全收益远大于调试成本。
这个方案不是理论最优,但在我手上的几个中大型项目里跑得很稳。如果你有更好的写法,或者遇到我没提到的坑,欢迎评论区交流。毕竟安全这事,多一个人讨论,就少一个漏洞。

暂无评论