在《 Android 文件共享安全指南:为何不能使用 file://》中我们提到:应用间共享文件时,应优先使用 ContentProvider(如 FileProvider)实现受控访问,以提升安全性。但如果在授权过程中处置不当,仍可能引发敏感数据泄露等安全风险。
本文将结合典型业务场景、授权流程、风险模型与最佳实践,系统性解释 FileProvider 授权的安全要点。
一、典型业务场景
应用 A 通过 FileProvider 向应用 B 分享日志文件:
Manifest 配置:
ini
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
代码授权:
java
private void shareLogFile(File logFile) {
Uri logUri = FileProvider.getUriForFile(
this,
"com.example.myapp.fileprovider",
logFile
);
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("text/plain");
shareIntent.putExtra(Intent.EXTRA_STREAM, logUri);
// 关键:授予对方临时读取权限
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(Intent.createChooser(shareIntent, "分享日志文件"));
}
授权行为主要通过以下两种方式触发。
二、两类授予权限方式
1.通过 Intent Flag 授权(常用)
- 授权对象:目标组件(单个 Activity/Service)****
- 权限生效:启动目标组件时瞬间授予****
2.使用 grantUriPermission() 主动授权
- 授权对象:目标包(整个 App)****
- 权限生效:调用后立即生效
差异点:Intent 授权仅对单个组件生效,而 grantUriPermission() 会让整个 App 获取权限,风险更高
以下是对两种授权方式及标志位的具体说明:
| 标志位 / 方法 | 示例代码 | 授权含义 | 适用场景 |
|---|---|---|---|
| FLAG_GRANT_READ_URI_PERMISSION | ![]() |
- 授予目标组件一次性 读权限 - 权限与对方 Activity 生命周期绑定 - 仅允许读取 该 URI 指向的单个文件,不能访问目录或其他文件 | 一次性分享图片、文件、日志等「只读」内容 |
| FLAG_GRANT_WRITE_URI_PERMISSION | ![]() |
- 授予目标组件一次性 写权限 (不自动包含读权限) - 允许修改 / 删除该 URI 指向的内容 | 目标 App 需要对该文件进行一次性编辑、修改或生成输出 |
| FLAG_GRANT_PREFIX_URI_PERMISSION | ![]() |
- 启用 目录级别授权 (前缀匹配) - 对方可访问以该前缀开头的所有子 URI - 权限范围从单文件扩大到"目录树",如授权的是content://com.example.demo.files/images,那么- content://com.example.demo.files/images/123及同目录和下级目录所有内容都可以访问 | 明确设计用于"目录级共享"的场景,例如文档管理、下载目录共享;必须严格限制前缀避免越权 |
| FLAG_GRANT_PERSISTABLE_URI_PERMISSION | ![]() |
- 本身并不是"权限",而是允许当次授权被 持久化 的"许可证" - 需搭配读/写权限使用 - 接收端需调用 takePersistableUriPermission() 才会变为长期授权 - 持久化后权限与 Activity 生命周期脱钩,由系统长期记录 |
文档访问、相册选取、文件选取器等长期需要访问某个 URI 的场景 |
| grantUriPermission() 方法 | ![]() |
- 系统立即将对应 URI 权限授予 整个包 ,无需启动组件 - 支持前缀 / 持久化等所有 flag 组合 - 适合"后台授予",无需用户交互 - 若希望持久化必须由对方主动调用 takePersistableUriPermission() |
对方长期需要访问某文件或配置数据,如导入/导出设置、长期持有的文档、文件型配置等 |
三、风险分析
上述的标志位或方法设置不当,可能导致超范围、能力、时间的权限共享,存在数据泄露风险,以下分析两种授权模式和授权期限各种组合的风险。
| 授权方式 | 示例代码 | 授权对象 | 授权生效时机 | 生命周期 | 风险 | 适用场景 |
|---|---|---|---|---|---|---|
| intent授权+临时授权标志 | ![]() |
单个组件实例(最小化) | 需要在intent启动对方组件时才生效,不启动则无效 | 随接收方组件销毁即撤销 | 最低 | 临时打开一个图片、文档 |
| Intent 授权 +持久化授权标志 | ![]() |
单次启动的组件,可持久化 真正是否持久化取决于接收端takePersistableUriPermission | 需要在intent启动对方组件时才生效,不启动则无效 | 可长期使用,与组件生命周期无关 | 中等 | ACTION_OPEN_DOCUMENT 相册/文件长期访问(安卓官方 SAF) |
| grantUriPermission+临时授权 | ![]() |
整个包(package-level) | 授权立刻生效,无需启动activity,无需用户交互 | 随应用运行或系统回收 | 中等(授权范围比 Intent 大很多) 权限范围明显更大,需严格限制使用。 | 特定合作方、固定目标包 |
| grantUriPermission +持久化授权 | ![]() |
整个包(package-level) 真正是否持久化取决于接收端takePersistableUriPermission | 授权立刻生效,无需启动activity,无需用户交互 | 随应用运行或系统回收 | 最高,等同于"长期向一个 App 开放文件" | 非常罕见,仅用于白名单合作方 |
四、错误案例
- 授权类型超限:只应该允许对方读的,同时给了对方不必要的写权限
- 授权范围超限:只应该授予对方单个组件权限,却给了对方整个包权限;只应该分享具体文件,但是共享了自己的某前缀所代表的目录;
- 授权时间超限:只应该授予对方临时权限,却给了对方永久权限
五、正确措施
整体原则就是:最小必要授权。
- 在类型上:只给对方必须得权限类型
- 在范围上:首选仅给单个组件授权;仅共享必要的具体文件
- 在期限上:首选临时权限,实在需要持久化授权的,根据应用实际情况,可以调用revokeUriPermission接口撤销授权,如android官方介绍所说

撤销授权的案例
java
// 授权并立即撤销 URI 权限的示例
public void openEditorWithTempPermission(Context context) {
// 1. 要共享给对方 App 的受保护内容 URI
Uri secureUri = Uri.parse("content://com.demo.secureprovider/private/doc/42");
// 2. 构造显式 Intent,指定目标应用和页面
Intent editIntent = new Intent();
editIntent.setComponent(new ComponentName(
"com.demo.doceditor", // 目标包名(第三方/自家 App)
"com.demo.doceditor.ui.EditDocumentActivity" // 目标 Activity 全类名
));
// 声明要操作的资源类型
editIntent.setDataAndType(secureUri, "application/pdf");
// 3. 通过 Intent 临时授予对方读写权限,并新开任务栈打开页面
editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION
| Intent.FLAG_ACTIVITY_NEW_TASK);
// 启动目标编辑页面
context.startActivity(editIntent);
// 4. 及时撤销上面授予 com.demo.doceditor 的 URI 读写权限
// 后续该应用再尝试通过这个 URI 访问时会因为缺乏权限而失败
context.revokeUriPermission(
"com.demo.doceditor", // 被授予过权限的目标包名
secureUri, // 同一个 URI
Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION
);
}
六、风险排查思路
什么情况下会有这种风险?以下几个条件同时满足:
- 使用ContentProvider(无论fileProvider还是自定义provider)
- 暴露给外部访问(exported=true或grantUriPermmision=true)
- 访问路径最终到达了provider内部敏感逻辑(query/openFile/call等),也就是这个授权行为会最终导致敏感数据泄露或敏感操作
所以可以先找到provider的定义处,在manifest中查看它的配置,是否设置grantUriPermissions="true"
ini
<provider android:name="com.example.demoContentProvider" android:exported="true" android:process=":temp" android:authorities="com.example.demo.contentProvider" android:grantUriPermissions="true"/>
如果怀疑存在授权行为,可检索 Provider 的 authorities 值。若应用确实进行了 URI 授权,该字段必然在相关代码中被引用。进一步确认授权的调用位置、数据流向以及处理逻辑,重点检查是否将 URI 分享给其他组件或外部应用,并核实权限授予是否遵循最小必要原则,避免过度授权。








