定义自定义应用权限

本文档介绍了应用开发者如何使用 Android 提供的安全功能定义自己的权限。通过定义自定义权限,应用可以与其他应用共享其资源和功能。如需详细了解权限,请参阅权限概览

背景

Android 是一种权限分离的操作系统,其中每个应用都以不同的系统身份(Linux 用户 ID 和组 ID)运行。系统的各个部分也会被分隔为不同的身份。因此,Linux 可以将应用同其他应用和系统隔离开来。

应用可以定义其他应用可请求的权限,从而将自己的功能提供给后者。它们还可以定义能够自动提供给已使用同一证书进行签名的任何其他应用的权限。

应用签名

所有 APK 都必须使用证书进行签名,证书的私钥由其开发者持有。证书无需由证书授权机构进行签名。Android 应用可以使用自签名证书,这种做法也十分普遍。Android 中的证书旨在区分应用创作者。这样,系统可以授予或拒绝应用对签名级权限的访问权限,以及同意或拒绝应用获取与另一应用相同的 Linux 身份的请求

在设备制造时间过后授予签名权限

从 Android 12(API 级别 31)开始,您可以在声明时利用签名级权限的 knownCerts 属性引用已知签名证书的摘要。

对于特定的签名级权限,您可以声明 knownCerts 属性,并在应用的 protectionLevel 中使用 knownSigner 标志。之后,如果某个发起请求的应用的签名谱系中的任何签名者(包括当前签名者)与使用 knownCerts 属性中的权限声明的某个摘要匹配,系统便会向这个发起请求的应用授予权限。

knownSigner 标志可让设备和应用向其他应用授予签名权限,而不必在设备制造和装运时为应用签名。

用户 ID 和文件访问权限

安装时,Android 会为每个软件包提供不同的 Linux 用户 ID。只要软件包在该设备上存续,在此期间其身份将保持不变。同一软件包在其他设备上可能会有不同的 UID;不过重要的是,每个软件包在指定设备上具有各不相同的 UID。

由于系统会在进程级别强制执行安全措施,因此任何两个软件包的代码通常都无法在同一进程中运行,因为它们需要以不同的 Linux 用户身份运行。

系统会为应用存储的所有数据分配该应用的用户 ID,而其他软件包通常无法访问这些数据。

如需详细了解 Android 的安全模型,请参阅 Android 安全性概览

定义并强制执行权限

如需强制执行自己的权限,您首先必须使用一个或多个 <permission> 元素在您的 AndroidManifest.xml 中声明它们。

命名规范

系统不允许多个软件包声明同名权限,除非所有软件包均使用同一证书进行签名。即使软件包声明了某个权限,系统也不会允许用户安装其他具有相同权限名称的软件包,除非这些软件包使用与第一个软件包相同的证书进行签名。

建议采用反向域名方式命名,在权限前面添加应用软件包名称,后跟 .permission.,接着是权限所代表功能的说明,并以 SNAKE_CASE 的大写格式书写。例如 com.example.myapp.permission.ENGAGE_HYPERSPACE

遵循此建议可以避免命名冲突,并有助于清晰地标识自定义权限的所有者和意图。

示例

例如,如果某个应用需要控制有哪些其他应用可以启动它的 activity,可以遵循以下方法为此操作声明权限:

<manifest
  xmlns:android="http://schemas.android.com/apk/res/android"
  package="com.example.myapp" >
    
    <permission
      android:name="com.example.myapp.permission.DEADLY_ACTIVITY"
      android:label="@string/permlab_deadlyActivity"
      android:description="@string/permdesc_deadlyActivity"
      android:permissionGroup="android.permission-group.COST_MONEY"
      android:protectionLevel="dangerous" />
    ...
</manifest>

protectionLevel 属性是必需属性,用于告知系统如何让用户知道哪些应用正在请求权限或者哪些应用可以获得该权限,如链接的文档中所述。

android:permissionGroup 属性是可选属性,仅用于帮助系统向用户显示权限。在大多数情况下,您可以将此属性设置为标准系统组(列在 android.Manifest.permission_group 中),不过您也可以自行定义组,如以下部分中所述。建议使用现有的组,因为这样可以简化向用户显示的权限界面。

您需要为权限提供标签和说明。这些是用户在查看权限列表 (android:label) 或有关单个权限的详细信息 (android:description) 时能够看到的字符串资源。标签非常简短,仅用几个词来描述该权限所保护的关键功能。说明则为几个句子,描述权限允许权限持有者执行哪些操作。我们通常会使用包含两个句子的说明:第一句用来描述权限;第二句则是提醒用户在向应用授予权限后可能会出现哪类错误。

以下示例展示了 CALL_PHONE 权限的标签和说明:

<string name="permlab_callPhone">directly call phone numbers</string>
<string name="permdesc_callPhone">Allows the app to call non-emergency
phone numbers without your intervention. Malicious apps may cause unexpected
calls on your phone bill.</string>

创建权限组

如上一部分中所示,您可以使用 android:permissionGroup 属性帮助系统向用户说明权限。在大多数情况下,您可以将此属性设置为标准系统组(列在 android.Manifest.permission_group 中),不过您也可以使用 <permission-group> 定义自己的组。

<permission-group> 元素用于为一组权限定义标签,这一组权限可以包括使用 <permission> 元素在清单中声明的权限和在其他位置声明的权限。此元素只会影响权限在向用户显示时的分组方式。<permission-group> 元素并不指定属于该组的权限,而是为该组提供名称。

将组名称分配给 <permission> 元素的 permissionGroup 属性可将权限放入组中。

<permission-tree> 元素为代码中定义的一组权限声明命名空间。

自定义权限建议

您可以为应用定义自定义权限,也可以通过定义 <uses-permission> 元素向其他应用请求自定义权限。不过,请仔细评估是否有必要这样做。

  • 如果要设计一套向彼此公开功能的应用,请尽可能将应用设计为每个权限仅定义一次。如果这些应用并非全都使用同一证书进行签名,您就必须这样做。即使应用均使用同一证书进行签名,最佳实践仍是每项权限仅定义一次。
  • 如果功能仅适用于与提供该功能的应用具有相同签名的应用,您或许可以使用签名检查功能来避免定义自定义权限。如果您的某个应用向您的另一个应用发出请求,后者会先验证两者是否使用相同的证书进行签名,只有证书相同时才会遵照该请求行事。

如果需要自定义权限,请考虑是否只有同一开发者签名的应用(作为执行权限检查的应用)才需要访问该权限,例如同一开发者在两个应用之间执行安全的进程间通信时。如果是这样,建议使用签名权限。签名权限对用户是透明的,可以避免已经过用户确认的权限,从而避免让用户感到困惑。

继续阅读以下内容:

<uses-permission>
声明应用所需系统权限的清单标记的 API 参考文档。

您可能还对以下内容感兴趣:

Android 安全性概览
详细讨论了 Android 平台安全模型。