关于 android-permissions-security
面向 Android 开发者的安全审计技能,指导发现权限配置和 IPC 边界中的漏洞,并给出最小权限修复做法。内容涵盖运行时权限、组件保护、URI 授权及调用方身份验证。
主要功能
- 01审计权限与 IPC 漏洞
- 02配置自定义权限保护级别
- 03限制导出组件和 URI 授权
- 04通过 Binder UID 验证调用方
- 05处理运行时权限请求
适用场景
- 01检查 ContentProvider 的读写权限划分
- 02修复可伪造的调用方身份检查
- 03收紧内容 URI 的授权范围
- 04改进运行时权限拒绝处理
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ android/ skills --skill android-permissions-security安装后在 Claude Code 中输入/android-permissions-security确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/android/guide/topics/manifest/permission-element.mdreferences/android/guide/topics/permissions/overview.mdreferences/android/privacy-and-security/risks/sender-of-pending-intents.mdreferences/android/training/permissions/requesting.md详细介绍
Android 生态系统中的权限漏洞审计、最小权限原则落实、IPC 边界防护以及运行时权限处理指南。
术语表
- 运行时权限: 危险权限(例如 Camera、Location),在 Android 6.0+(API 23+)上必须由用户在运行时授予。
- 自定义权限: 应用定义的权限(
<permission>),用于限制对其已导出组件的访问。 - 保护级别: 定义操作系统如何授予权限的属性(
normal、dangerous、signature、knownSigner)。 - knownSigner: 保护标志(API 31+),向证书摘要与可信证书摘要匹配的合作应用授予签名级访问权限。
- SigningInfo: 对象(API 28+),提供应用的签名历史和多个签名证书。
- Binder 调用方身份: 使用
Binder.getCallingUid()获取的、可靠地代表调用进程的 UID。 - URI 权限: 使用
grantUriPermissions和 Intent 标志授予的、对内容 URI 的临时细粒度访问权限。
前置条件
- 在应用级
build.gradle文件中添加androidx.core:core-ktx和androidx.activity:activity-ktx(或androidx.fragment:fragment-ktx)依赖,以使用ContextCompat和ActivityResultContracts。 - 对于
knownSigner合作方权限(android:knownCerts),确保项目的compileSdk和targetSdk至少为 31(Android 12)。 - 对于广播身份共享(
BroadcastOptions.setShareIdentityEnabled),确保compileSdk和targetSdk至少为 34(Android 14)。
1. 安全审计和权限缺口检测指南
审计 Android 项目的权限和 IPC 安全漏洞时,请系统地检查各个方面:
| 漏洞领域 | 搜索位置 / 模式 | 风险 | 修复措施 |
|---|---|---|---|
| 自定义权限保护薄弱 | AndroidManifest.xml 中的 android:protectionLevel="normal"、"dangerous" 或 "signatureOrSystem" | 未经授权的应用可不受限制地请求或声明权限 | 升级为 signature 或 `signature |
| URI 权限授予范围过广 | <provider> 中的 <grant-uri-permission android:pathPrefix="/" /> 或 pathPrefix="" | 向第三方泄露提供程序的全部数据 | 替换为明确限定范围的子路径(例如 android:pathPrefix="/shared/")。保留 android:grantUriPermissions="true"。移除所有通配符 pathPrefix="/"。 |
| ContentProvider 缺少读写权限分离 | <provider> 仅配置通用 android:permission,或未配置 android:readPermission 和 android:writePermission | 只读调用方执行写操作,或只写调用方访问读取操作 | 配置细粒度的 android:readPermission="...READ_DATA" 和 android:writePermission="...WRITE_DATA",并在 <manifest> 根节点声明带有 android:protectionLevel="signature"(或 `signature |
| 可伪造的调用方身份检查 | Service 中的 intent.getStringExtra("calling_package")、intent.getStringExtra("sender")、callingPackage == "..." | 恶意调用方伪造 Intent extra 或字符串 | 移除字符串检查;使用 Binder.getCallingUid() 和 pm.hasSigningCertificate() 以加密方式验证调用方身份。 |
| 绑定服务权限绕过 | Binder AIDL stub 中的 checkCallingOrSelfPermission() 或 enforceCallingOrSelfPermission() | 在 IPC 外或身份被清除时,会回退到宿主应用 UID,导致“困惑代理”绕过 | 替换为 enforceCallingPermission("...WRITE_DATA", ...) 或 checkCallingPermission("...WRITE_DATA")。 |
| 绑定服务缺少方法级检查 | 修改状态的绑定服务 AIDL 方法未强制检查写入权限 | 具有只读绑定访问权限的调用方可执行会修改数据的写操作 | 在会修改数据的 AIDL 方法中添加 enforceCallingPermission("...WRITE_DATA", "Caller lacks WRITE_DATA permission")。 |
| 运行时权限流程有缺陷 | 缺少 shouldShowRequestPermissionRationale()、缺少永久拒绝处理,或在循环中重新请求权限 | 用户体验不佳、无限循环或无法恢复权限 | 实现三状态流程:检查是否已授权 -> 检查是否需要显示说明对话框 -> 启动契约。永久拒绝时,跳转至 Settings.ACTION_APPLICATION_DETAILS_SETTINGS。 |
| 同时请求位置权限 | 在一次请求中使用 arrayOf(ACCESS_FINE_LOCATION, ACCESS_BACKGROUND_LOCATION) | Android 11+(API 30+)会拒绝 | 首先请求前台位置权限(ACCESS_FINE_LOCATION、ACCESS_COARSE_LOCATION)。仅在前台权限获批后,通过单独的用户操作请求 ACCESS_BACKGROUND_LOCATION。 |
| 媒体存储权限范围过广 | 为选择用户图片而请求 READ_EXTERNAL_STORAGE 或 READ_MEDIA_IMAGES | 权限过度,侵犯用户隐私 | 迁移到零权限 Photo Picker(ActivityResultContracts.PickVisualMedia)。 |
| 缓存权限状态 | 将权限状态存储在 var cachedLocationPermissionGranted = ... 中 | 权限被撤销或过期时会失效 | 在每次调用时动态查询 ContextCompat.checkSelfPermission(),并使用 try-catch 块包裹受保护的调用,以捕获 SecurityException。 |
| 不安全的广播 | 调用 sendBroadcast(intent) 时未设置目标包或接收方权限;接收方未验证发送方身份 | 数据被拦截或命令被伪造 | 设置显式包(intent.setPackage(...)),传递接收方权限,并启用 BroadcastOptions.setShareIdentityEnabled(true)(API 34+)以验证 sentFromPackage。 |
2. 自定义权限与保护级别
在清单中声明自定义权限或合作伙伴证书时,必须遵循 <permission> 元素指南中的说明。
signature:限制只有使用相同开发者密钥签名的应用才能访问。signature|knownSigner(API 31+):限制只有签名证书摘要与android:knownCerts中声明的哈希值匹配的合作伙伴应用才能访问。normal或dangerous:不要用于私有的应用间 IPC。signatureOrSystem:已弃用(API 23)。将已弃用的signatureOrSystem权限升级为signature。避免在非系统应用中使用。
清单片段
签名级别的自定义权限
通过在清单中声明 <permission> 并对目标组件应用 android:permission,使用 signature 权限将组件访问限制为仅允许使用相同开发者证书签名的应用。
<permission
android:name="com.example.snippets.permission.ACCESS_SECURE_API"
android:protectionLevel="signature" />
KnownSigner 合作伙伴权限(API 31+)
通过在清单中声明带有 android:knownCerts 的 <permission>,并让其引用可信 SHA-256 证书摘要的资源数组,使用 signature|knownSigner 权限向指定的外部合作伙伴应用授予访问权限。
<resources xmlns:tools="http://schemas.android.com/tools">
<string-array name="trusted_partner_certs" tools:ignore="Typos">
<item>103938ee4537e59e8ee792f654504fb8346fc6b346d0bbc4415fc339fcfc8ec1</item>
</string-array>
</resources>
<permission
android:name="com.example.snippets.permission.PARTNER_API"
android:protectionLevel="signature|knownSigner"
android:knownCerts="@array/trusted_partner_certs" />
3. 导出组件保护与 URI 权限
- 私有组件:始终设置
android:exported="false"。 - 导出组件:要求设置权限(
android:permission、android:readPermission、android:writePermission)。 - 细粒度 URI 授权:保留
android:grantUriPermissions="true"以支持安全共享,但将授权范围限定为特定子路径。切勿使用通配符pathPrefix="/"。 - 自定义权限声明:在 Provider 上拆分读写权限时,必须在
<manifest>根级别声明带有android:protectionLevel="signature"(或signature|knownSigner)的<permission>标签。 - 修复现有通配符授权:必须查找并移除所有现有的
<grant-uri-permission android:pathPrefix="/" />或pathPrefix="",并将其替换为明确限定范围的子路径(例如android:pathPrefix="/shared/")。 - 细粒度
UriPermission管理:不要使用宽泛的清单授权,而应使用运行时android.content.UriPermission令牌,以遵循最小权限原则按 URI 共享:- 授予临时访问权限:
context.grantUriPermission(targetPackage, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION),或将Intent.FLAG_GRANT_READ_URI_PERMISSION附加到发出的 Intent。 - 持久化访问权限:接收应用调用
contentResolver.takePersistableUriPermission(uri, modeFlags),以便在重启后仍能持续访问。 - 审核活动授权:检查
contentResolver.persistedUriPermissions(List<UriPermission>),验证已授权的 URI(uriPermission.uri、uriPermission.isReadPermission()、uriPermission.isWritePermission())。 - 撤销访问权限:操作完成后,立即调用
context.revokeUriPermission(uri, modeFlags)或contentResolver.releasePersistableUriPermission(uri, modeFlags)。
- 授予临时访问权限:
清单片段:完整的安全 ContentProvider 声明
在 <manifest> 根级别声明拆分后的读写权限:
<permission
android:name="com.example.snippets.permission.READ_DATA"
android:protectionLevel="signature" />
<permission
android:name="com.example.snippets.permission.WRITE_DATA"
android:protectionLevel="signature" />
然后使用这些权限配置 Provider,并设置限定范围的 URI 授权:
<provider
android:name=".permissions.SecureDataProvider"
android:authorities="com.example.snippets.partnerprovider"
android:exported="true"
android:readPermission="com.example.snippets.permission.READ_DATA"
android:writePermission="com.example.snippets.permission.WRITE_DATA"
android:grantUriPermissions="true">
<grant-uri-permission android:pathPrefix="/shared/" />
</provider>
运行时细粒度 UriPermission 管理片段
object UriPermissionManager {
/** Grants temporary read access to a specific URI for a partner package. */
fun grantScopedUriAccess(context: Context, targetPackage: String, uri: Uri) {
context.grantUriPermission(
targetPackage,
uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION
)
}
/** Audits active persisted URI permissions held by the application. */
fun getActivePersistedPermissions(context: Context): List<UriPermission> {
return context.contentResolver.persistedUriPermissions
}
/** Revokes temporary URI access when data transfer completes. */
fun revokeScopedUriAccess(context: Context, uri: Uri) {
context.revokeUriPermission(
uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
}
}
4. 以密码学方式验证调用方身份
绝不要信任 Intent 或字符串参数中提供的调用方软件包名称
(getCallingPackage()、intent.getStringExtra("calling_package")、
intent.getStringExtra("sender")、intent.getStringExtra("package"))。恶意应用可以伪造 Intent
额外数据和字符串形式的软件包名称。
严重漏洞:利用 Intent 额外数据伪造软件包名称
// VULNERABLE PATTERN: DO NOT DO THIS
val callingPackage = intent?.getStringExtra("calling_package")
if (callingPackage != null && SignatureUtils.verifyPartnerPackage(this, callingPackage)) {
// A malicious app passes "com.example.partner" in the extra.
// The signature check verifies the installed partner on disk, but the CALLER was malicious!
}
### 安全模式:以加密方式验证调用方 UID
- 彻底移除所有
intent.getStringExtra("calling_package")、intent.getStringExtra("sender")和callingPackage == "..."检查。 - 在活动的 Binder IPC 事务中(例如 AIDL
Stub方法或ContentProvider方法)使用Binder.getCallingUid()获取调用方身份。 - 使用
PackageManager.hasSigningCertificate()(API 28+)或PackageManager.getPackagesForUid()验证调用方证书。
完整的 CallerVerifier 实现
将此实现应用于当前执行易被伪造检查的服务。
- 放置位置:在你要修复的服务的同一个文件中声明
CallerVerifier(例如,在SecureDataService.kt内)。不要将验证逻辑移到新文件中,而让原服务保持不变。 - 活动的 Binder IPC 上下文:
Binder.getCallingUid()仅在活动的 Binder 事务期间返回远程调用方的 UID(绝不能在Service.onStartCommand()或Service.onBind()中调用;此时它会返回Process.myUid())。拒绝callingUid == Process.myUid(),确保在活动 IPC 事务之外或调用Binder.clearCallingIdentity()之后的调用会以失败关闭,而不是绕过验证。 - 可空性:
packageInfo.signatures的类型为Array<Signature>?。请严格照抄下方的?: return false保护语句。直接遍历packageInfo.signatures会因“Non-nullable value required to call an 'iterator()' method in a for-loop.”而无法编译。
object CallerVerifier {
private const val TRUSTED_PARTNER_SHA256 =
"A1B2C3D4E5F60708090A0B0C0D0E0F1011121314151617181920212223242526"
fun isCallerAuthorized(context: Context): Boolean {
val callingUid = Binder.getCallingUid()
if (callingUid == android.os.Process.myUid()) return false
val pm = context.packageManager
// Modern API 28+ check by UID:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
val certBytes = hexStringToByteArray(TRUSTED_PARTNER_SHA256)
return pm.hasSigningCertificate(callingUid, certBytes, PackageManager.CERT_INPUT_SHA256)
}
// Fallback for legacy APIs:
val callingPackages = pm.getPackagesForUid(callingUid) ?: return false
for (pkg in callingPackages) {
if (verifyPackageSignature(pm, pkg)) {
return true
}
}
return false
}
@Suppress("DEPRECATION")
private fun verifyPackageSignature(pm: PackageManager, packageName: String): Boolean {
return try {
val packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES)
val signatures = packageInfo.signatures ?: return false
for (sig in signatures) {
val digest = java.security.MessageDigest.getInstance("SHA-256").digest(sig.toByteArray())
val hex = digest.joinToString("") { "%02X".format(it) }
if (hex.equals(TRUSTED_PARTNER_SHA256, ignoreCase = true)) return true
}
false
} catch (e: PackageManager.NameNotFoundException) {
false
}
}
private fun hexStringToByteArray(s: String): ByteArray {
val len = s.length
val data = ByteArray(len / 2)
for (i in 0 until len step 2) {
data[i / 2] = ((Character.digit(s[i], 16) shl 4) + Character.digit(s[i + 1], 16)).toByte()
}
return data
}
}
5. 绑定服务中的方法级 Binder 强制执行(SecureBoundService.kt)
对于公开 AIDL Binder 存根的绑定服务,请使用
enforceCallingPermission() 保护会修改状态的方法。
关键安全规则:绝不要在服务中使用 CallingOrSelfPermission
- 绝不要使用
checkCallingOrSelfPermission()或enforceCallingOrSelfPermission()。 - **原因:**在活动的 Binder 事务期间,此方法会检查调用方的权限。但是,如果在本地调用该方法(不在活动 IPC 期间),或 IPC 调用身份已被清除,该方法就会退而检查宿主应用的权限。由于宿主应用拥有该权限,这种回退会授予访问权限并绕过安全边界,从而造成 Confused Deputy 漏洞。
- 必须使用:
enforceCallingPermission("com.example.snippets.permission.WRITE_DATA", "Caller lacks WRITE_DATA permission")或checkCallingPermission("com.example.snippets.permission.WRITE_DATA")。
完整的 SecureBoundService 实现
class SecureBoundService : Service() {
private val binder = object : IMyService.Stub() {
override fun getData(): String {
// Read-only operation guarded by manifest-level permission
return "Confidential Data"
}
override fun modifyData(newData: String) {
// MUST use enforceCallingPermission or checkCallingPermission.
// NEVER use checkCallingOrSelfPermission or enforceCallingOrSelfPermission.
[email protected](
"com.example.snippets.permission.WRITE_DATA",
"Caller lacks WRITE_DATA permission"
)
updateInternalState(newData)
Log.d("SecureBoundService", "Data modified to: $newData with proper WRITE_DATA permission check")
}
}
override fun onBind(intent: Intent?): IBinder = binder
private fun updateInternalState(data: String) {
// Internal state update logic
}
}
6. 安全的运行时权限工作流(RuntimePermissionsActivity.kt)
实现运行时权限请求或处理权限拒绝状态时,必须遵循请求运行时权限指南 和权限概览中的说明。实现一个 3 状态流程,不缓存状态,也不创建无限重试循环:
- 检查已授权状态:
ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED。 - 检查理由:使用
shouldShowRequestPermissionRationale(permission)在请求权限前提供说明。 - 启动契约:
ActivityResultContracts.RequestPermission()。 - 处理永久拒绝:在回调中,如果权限被拒绝且
!shouldShowRequestPermissionRationale(permission),请引导用户前往Settings.ACTION_APPLICATION_DETAILS_SETTINGS。
完整的 RuntimePermissionsActivity.kt 实现
class RuntimePermissionsActivity : ComponentActivity() {
private val cameraLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
if (isGranted) {
startCameraPreview()
} else {
if (!shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) {
// User selected 'Don't ask again' or permanently denied.
// Direct user to Application Details Settings.
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
data = Uri.fromParts("package", packageName, null)
}
startActivity(intent)
} else {
showSnackbar("Camera permission is required to preview camera feed.")
}
}
}
fun requestCameraPermissionSafely() {
val permission = Manifest.permission.CAMERA
when {
ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED -> {
startCameraPreview()
}
shouldShowRequestPermissionRationale(permission) -> {
showSnackbar("Camera permission is needed to preview camera feed.")
cameraLauncher.launch(permission)
}
else -> {
cameraLauncher.launch(permission)
}
}
}
private fun startCameraPreview() {
Log.d("RuntimePermissionsActivity", "Camera preview started")
}
private fun showSnackbar(msg: String) {
Log.i("RuntimePermissionsActivity", msg)
}
}
7. 零权限替代方案和顺序流程
实现照片或媒体选择时,必须遵循照片选择器 API 指南中的说明,避免请求广泛的存储权限。
- 照片选择器:优先使用
ActivityResultContracts.PickVisualMedia(),而不是请求广泛的存储权限(READ_MEDIA_IMAGES、READ_EXTERNAL_STORAGE)。 - 顺序位置请求流程:首先请求
ACCESS_FINE_LOCATION和ACCESS_COARSE_LOCATION。切勿同时请求ACCESS_BACKGROUND_LOCATION。只有在获得前台位置权限后,才能在单独的步骤中请求后台位置权限。
照片选择器实现
private val photoPickerLauncher =
registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri ->
if (uri != null) {
handleImageUri(uri)
}
}
fun selectPhoto() {
photoPickerLauncher.launch(
PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)
)
}
顺序位置请求实现
private val foregroundLocationLauncher =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
val fineGranted = permissions[Manifest.permission.ACCESS_FINE_LOCATION] ?: false
val coarseGranted = permissions[Manifest.permission.ACCESS_COARSE_LOCATION] ?: false
if (fineGranted || coarseGranted) {
startForegroundLocationUpdates()
}
}
fun requestForegroundLocation() {
foregroundLocationLauncher.launch(
arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
)
)
}
// Background location requested only AFTER foreground is granted and user explicitly opts in:
private val backgroundLocationLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
if (isGranted) {
startBackgroundTracking()
}
}
8. 无状态检查和错误边界
- 切勿缓存权限状态到内存变量中(例如
cachedLocationPermissionGranted)。每次操作前都应动态调用ContextCompat.checkSelfPermission()进行检查。 - 使用 try-catch 包装 API 调用:在受权限保护的框架调用(例如位置查询)周围捕获
SecurityException,以妥善处理一次性权限被撤销的情况。
fun performLocationAccess() {
val fine = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
val coarse = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION)
if (fine != PackageManager.PERMISSION_GRANTED && coarse != PackageManager.PERMISSION_GRANTED) {
requestForegroundLocation()
return
}
val provider = if (fine == PackageManager.PERMISSION_GRANTED) {
LocationManager.GPS_PROVIDER
} else {
LocationManager.NETWORK_PROVIDER
}
try {
val location = locationManager.getLastKnownLocation(provider)
processLocation(location)
} catch (e: SecurityException) {
Log.e("LocationAccess", "Permission revoked at runtime", e)
}
}
9. 安全广播(API 34+ 共享身份)
使用显式 Intent、所需的接收器权限或 Android 14+ 广播选项:
// Sender: Enforce permission and share identity
val intent = Intent("com.example.snippets.permission.ACTION_SECRET_UPDATE").apply {
setPackage("com.example.partner") // Explicit target
}
val isUdc = Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
val options = if (isUdc) {
BroadcastOptions.makeBasic().apply {
setShareIdentityEnabled(true)
}.toBundle()
} else null
sendBroadcast(intent, "com.example.snippets.permission.RECEIVE_SECRET_UPDATE", options)
// Receiver: Validate sender on Android 14+
class ProtectedReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == "com.example.snippets.permission.ACTION_SECRET_UPDATE") {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
val sender = sentFromPackage
if (sender != "com.example.trusted_sender") {
return // Reject unauthorized sender
}
}
processUpdate(intent)
}
}
private fun processUpdate(intent: Intent) {}
}
旧版兼容性(API 34 之前):在 API 33 及更低版本上,切勿使用 PendingIntent.getCreatorPackage() 或 PendingIntent.getCreatorUid() 验证广播发送方,因为任何获取了受信任应用 PendingIntent 的应用都能附加内容并发送它(参见Pending Intent 的发送方)。
请改为在清单中使用 signature 或 signature|knownSigner(API 31+)权限限制 <receiver>;如果 minSdkVersion 低于 31,则使用绑定的 Service 或 ContentProvider,并验证 Binder.getCallingUid()。
质量检查清单
应做事项
- 必须在
<manifest>根节点为自定义权限和 ContentProvider 拆分权限声明<permission android:name="..." android:protectionLevel="signature" />(或signature|knownSigner)。 - 必须为所有私有组件显式设置
android:exported="false",以免需要管理自定义权限。 - 必须使用
Binder.getCallingUid()和证书哈希(PackageManager.hasSigningCertificate或getPackagesForUid)以加密方式验证调用方,而不是检查可被伪造的 Intent 额外数据。 - 必须在运行时权限提示之前和处理拒绝时检查
shouldShowRequestPermissionRationale()。 - 必须在权限被永久拒绝时引导用户前往
Settings.ACTION_APPLICATION_DETAILS_SETTINGS。 - 必须在可修改数据的 AIDL 方法上使用
enforceCallingPermission()或checkCallingPermission()执行方法级检查。 - 必须将 ContentProvider URI 权限限定到明确的子路径(例如
android:pathPrefix="/shared/"),同时保留android:grantUriPermissions="true"。 - 必须在受保护的 API 调用周围捕获
SecurityException。 - 必须按顺序请求前台和后台位置权限,切勿同时请求。
切勿
- 切勿从 Intent extra 中提取或验证包名(
intent.getStringExtra("calling_package")、intent.getStringExtra("sender")、intent.getStringExtra("package"))。始终使用Binder.getCallingUid()和证书进行身份验证。 - 切勿使用
PendingIntent.getCreatorPackage()或PendingIntent.getCreatorUid()对广播或 IPC 发送方进行身份验证。 - 切勿在保留可伪造的 Intent extra 检查的同时,仅通过添加清单权限来补救调用方伪造问题。
- 切勿使用
checkCallingOrSelfPermission()或enforceCallingOrSelfPermission()来保护外部调用方方法。始终使用enforceCallingPermission()或checkCallingPermission()。 - 切勿使用已弃用的
signatureOrSystem。 - 切勿声明或保留
<grant-uri-permission android:pathPrefix="/" />或pathPrefix=""。始终将其替换为范围受限的子路径。 - 切勿在权限请求被拒绝后无限循环请求权限。
- 切勿在同一次请求中同时请求前台和后台位置权限。
- 切勿在内存字段中缓存权限授予状态。