Android官方文档—引言(系统权限)

系统权限

Android是一个权限分离的操作系统,其中每个应用程序以不同的系统标识运行(Linux用户标识和组标识)。系统的某些部分也被分成不同的身份。因此,Linux将应用程序彼此隔离并与系统隔离。

通过“权限”机制提供额外的更细粒度的安全功能,该机制强制限制特定进程可以执行的特定操作,和per-URI权限,用于授予对特定数据的临时访问权限。

本文档描述了应用程序开发人员如何使用Android提供的安全功能。 Android Security Overview中提供了更为通用的Android安全概述。

安全架构


 Android安全体系结构的核心设计点是,默认情况下,任何应用程序都无权执行任何会对其他应用程序,操作系统或用户产生负面影响的操作。这包括读取或写入用户的私人数据(如联系人或电子邮件),读取或写入其他应用程序的文件,执行网络访问,保持设备唤醒等。

因为每个Android应用程序都在进程沙箱中运行,应用程序必须明确共享资源和数据。他们通过声明权限来使用普通沙箱未提供的功能。应用程序静态声明所需的权限,Android系统会提示用户同意。

应用程序沙箱不依赖于用于构建应用程序的技术。尤其是Dalvik VM不是安全边界,任何应用程序都可以运行原生代码(请参阅Android NDK)。所有类型的应用程序(Java,本机和混合应用程序)都以相同的方式进行沙盒化,并且具有相同的安全性。

应用程序签名


所有APK(.apk文件)必须使用其私钥由其开发人员持有的证书进行签名。此证书标识应用程序的作者。证书不需要由证书颁发机构签名;对于Android应用程序来说,使用自签名证书是完全允许的,也是通用的方式。Android中证书的目的是区分应用程序作者。这允许系统授予或拒绝应用程序访问签名级权限,并授予或拒绝应用程序的请求与另一个应用程序具有相同的Linux身份。

用户ID和文件访问权限


在应用程序安装时,Android为每个包提供了一个独特的Linux用户ID。在程序包的生命周期内,该标识保持不变。在不同的设备上,同一个包可能有不同的UID。

由于安全检验发生在进程级别,因此任何两个软件包的代码通常不能在同一进程中运行,因为它们需要作为不同的Linux用户运行。您可以使用每个包的AndroidManifest.xml manifest标签中的sharedUserId属性为它们分配相同的用户ID,这样一来,出于安全考虑,两个包被视为具有相同用户ID和文件权限的相同应用程序。请注意,为了保持安全性,只有两个使用相同签名(并请求相同的sharedUserId)签名的应用程序将被赋予相同的用户ID。

应用程序存储的任何数据都将分配该应用程序的用户ID,而其他软件包通常无法访问。使用getSharedPreferences(String,int),openFileOutput(String,int)或openOrCreateDatabase(String,int,SQLiteDatabase.CursorFactory)创建新文件时,可以使用MODE_WORLD_READABLE和/或MODE_WORLD_WRITEABLE标志来允许任何其他包读取/写文件。设置这些标志时,该文件仍由您的应用程序拥有,但其全局读取和/或写入权限已正确设置,因此任何其他应用程序都可以看到它。

使用权限 


默认情况下,基本的Android应用程序没有与之关联的权限,这意味着它无法执行任何会对用户体验或设备上的任何数据产生负面影响的操作。要使用设备的受保护功能,您必须在您app的清单文件中包含一个或多个<uses-permission>标记。

扫描二维码关注公众号,回复: 3513529 查看本文章

例如,需要监视传入SMS消息的应用程序将指定:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.android.app.myapp" >
    <uses-permission android:name="android.permission.RECEIVE_SMS" />
    ...
</manifest>

如果您的应用在其清单中列出了正常权限(即,不会对用户的隐私或设备操作造成太大风险的权限),系统会自动授予这些权限。如果您的应用在其清单中列出了危险权限(即可能会影响用户隐私或设备正常运行的权限),系统会要求用户明确授予这些权限。Android发出请求的方式取决于系统版本以及应用所针对的系统版本:

  • 如果设备运行的是Android 6.0(API级别23)或更高版本,并且应用程序的targetSdkVersion为23或更高,则应用程序会在运行时请求用户的权限。用户可以随时撤消权限,因此应用程序需要在每次运行时检查它是否具有权限。有关在应用程序中请求权限的详细信息,请参阅Working with System Permissions培训指南。
  • 如果设备运行的是Android 5.1(API级别22)或更低版本,或者应用程序的targetSdkVersion为22或更低,则系统会要求用户在用户安装应用程序时授予权限。如果您向应用程序的更新版本添加新权限,系统会要求用户在用户更新应用程序时授予该权限。用户安装应用程序后,他们撤销权限的唯一方法是卸载应用程序。

通常,权限获取失败会导致将SecurityException抛回应用程序。但是,并不能保证任何情况下都能发生。例如,sendBroadcast(Intent)方法在方法调用返回后将数据传递给每个接收者时检查权限,如果存在权限失败,您将不会收到异常。但是,在大多数情况下,都会在系统日志中打印权限失败。

可以在Manifest.permission中找到Android系统提供的权限。任何应用程序也可以定义和强制执行自己的权限,因此并不是所有的权限列出来了。

在程序运行期间,可以在许多地方强制执行特定权限:

  • 在调用系统时,防止应用程序执行某些功能。
  • 启动活动时,阻止应用程序启动其他应用程序的活动。
  • 发送和接收广播,控制谁可以接收您的广播或谁可以向您发送广播。
  • 访问和操作content provider时。
  • 绑定或启动服务。

自动权限调整

随着时间的推移,可能会向平台添加新的限制,以便为了使用某些API,您的应用必须请求以前不需要的权限。假设已有的应用程序可以正常访问这些API,因此Android可以将新的权限请求应用于应用程序的清单,以避免在新平台版本上破坏应用程序。Android根据为targetSdkVersion属性提供的值,决定应用是否可能需要该权限。如果该值低于添加权限的版本,则Android会添加权限。

例如,在API级别4中添加了WRITE_EXTERNAL_STORAGE权限,以限制对共享存储空间的访问。如果您的targetSdkVersion为3或更低,则此权限会在较新版本的Android上添加到您的应用中。

警告:如果您的应用自动添加了权限,则Google Play上的应用列表会列出这些附加权限,即使您的应用可能实际上并不需要这些权限。

要避免这种情况并删除您不需要的默认权限,请始终将targetSdkVersion更新为尽可能高。您可以在Build.VERSION_CODES文档中查看每个版本添加了哪些权限。

正常权限和危险权限


系统权限分为几个保护级别。要了解的两个最重要的保护级别是正常和危险的权限:

  • 普通权限涵盖了应用程序需要访问应用程序沙箱外部数据或资源的区域,但用户隐私或其他应用程序操作的风险非常小。例如,设置时区的权限是正常权限。如果应用声明它需要正常权限,系统会自动向应用授予权限。有关当前正常权限的完整列表,请参阅Normal permissions
  • 危险权限涵盖应用程序需要涉及用户私人信息的数据或资源的区域,或者可能会影响用户存储的数据或其他应用程序的操作。例如,读取用户联系人的权限是一种危险的权限。如果应用声明它需要危险权限,则用户必须明确授予该应用的权限。
  • 特殊权限,有一些权限不像正常和危险的权限。SYSTEM_ALERT_WINDOW和WRITE_SETTINGS特别敏感,因此大多数应用程序不应使用它们。如果应用程序需要其中一个权限,则必须在清单中声明权限,并发送请求用户授权的意图。系统通过向用户显示详细的管理页来响应你的请求。有关如何请求这些权限的详细信息,请参阅SYSTEM_ALERT_WINDOW和WRITE_SETTINGS参考条目。

权限组

所有危险的Android系统权限都属于权限组。如果设备运行的是Android 6.0(API级别23)且应用的targetSdkVersion为23或更高,则当您的应用请求危险权限时,以下系统行为适用:

  • 如果应用程序请求其清单中列出的危险权限,并且该应用程序当前在权限组中没有任何权限,则系统会向用户显示一个对话框,描述该应用程序要访问的权限组。该对话框不会显示该组中的具体权限。例如,如果应用程序请求READ_CONTACTS权限,则系统对话框只显示应用程序需要访问设备的联系人。如果用户授予批准,系统将为应用程序提供其请求的权限。
  • 如果应用程序请求其清单中列出的危险权限,并且该应用程序已在同一权限组中具有其他危险权限,则系统会立即授予权限,而不与该用户进行任何交互。例如,如果应用程序先前已请求并已获得READ_CONTACTS权限,然后它请求WRITE_CONTACTS,则系统立即授予该权限。

任何权限都属于权限组,包括应用程序定义的普通权限和权限。但是,只有危险权限影响用户体验。使用正常权限时,您可以忽略权限组。

如果设备运行的是Android 5.1(API级别22)或更低版本,或者应用程序的targetSdkVersion为22或更低,系统会要求用户在安装时授予权限。系统仅仅告诉用户应用程序需要哪些权限组,而不是具体某个权限。

表1.危险权限和权限组。

权限的定义与执行


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

例如,应用程序想要控制谁可以启动其某个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>

注意:除非所有软件包都使用相同的证书进行签名,否则系统不允许多个软件包声明具有相同名称的权限。系统不允许用户安装具有相同权限名称的其他包,除非这些包使用与第一个包相同的证书进行签名。为避免命名冲突,我们建议对自定义权限使用反向域式命名,例如com.example.myapp.ENGAGE_HYPERSPACE。

必须使用protectionLevel属性,告诉系统如何通知用户需要许可的应用程序,或允许谁持有该权限,如链接文档中所述。android:permissionGroup属性是可选的,仅用于帮助系统向用户显示权限。在大多数情况下,您可以将其设置为标准系统组(在android.Manifest.permission_group中列出),尽管您可以自己定义一个组。最好使用现有组,因为这简化了向用户显示的权限请求UI。

您需要提供权限的标签和说明。这些是字符串资源,用户在查看权限列表(android:label)或单个权限的详细信息(android:description)时可以看到。标签应该很简短;仅仅描述权限保护的关键功能的几个词。描述应该是一些描述许可允许持有人做什么的句子。我们的约定是两句话描述:第一句描述权限,第二句警告用户如果申请被授予许可可能受影响的事物类型。

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

<string name="permlab_callPhone">directly call phone numbers</string>
<string name="permdesc_callPhone">Allows the application to call
    phone numbers without your intervention. Malicious applications may
    cause unexpected calls on your phone bill. Note that this does not
    allow the application to call emergency numbers.</string>

您可以使用“设置”应用程序和shell命令adb shell pm list权限查看当前在系统中定义的权限。要使用“设置”应用,请转到“设置”>“应用程序”。选择一个应用并向下滚动以查看该应用使用的权限。对于开发人员,adb'-s'选项以类似于用户将看到它们的形式显示权限:

$ adb shell pm list permissions -s
All Permissions:

Network communication: view Wi-Fi state, create Bluetooth connections, full
Internet access, view network state

Your location: access extra location provider commands, fine (GPS) location,
mock location sources for testing, coarse (network-based) location

Services that cost you money: send SMS messages, directly call phone numbers

...

自定义权限建议

通过定义<uses-permission>标签,应用程序可以定义自己的自定义权限并从其他应用请求自定义权限。但是,您应该仔细评估您的应用是否有必要这样做。

  • 如果您正在设计一组彼此公开功能的应用程序,请尝试设计应用程序,以便每个权限仅定义一次。如果应用程序未使用相同的证书进行签名,则必须执行此操作。即使应用程序都使用相同的证书进行签名,最佳做法是仅定义一次每个权限。
  • 如果该功能仅适用于使用与提供应用程序相同的签名签名的应用程序,则可以避免使用签名检查来定义自定义权限。当您的某个应用向其他应用发出请求时,第二个应用可以在遵守请求之前验证这两个应用是否都使用相同的证书进行签名。
  • 如果您正在开发仅在您自己的设备上运行的应用程序套件,则应开发并安装管理套件中所有应用程序权限的程序包。该软件包本身不需要提供任何服务。它只声明所有权限,套件中的其他应用程序使用<uses-permission>标签请求这些权限。

在AndroidManifest.xml中强制执行权限

您可以通过在AndroidManifest.xml中使用高级权限来限制访问系统或应用程序的整个组件。为此,请在所需组件上包含android:permission属性,命名控制对其进行访问的权限。

Activity权限(使用<activity>标记)会限制谁可以启动相关活动。在Context.startActivity()和Activity.startActivityForResult()期间检查权限;如果调用者没有所需的权限,则从调用中抛出SecurityException。

Service权限(使用<service>标记)限制谁可以启动或绑定到关联的服务。在Context.startService(),Context.stopService()和Context.bindService()期间检查权限;如果调用者没有所需的权限,则从调用中抛出SecurityException。

BroadcastReceiver权限(使用<receiver>标记)限制谁可以向相关接收者发送广播。在Context.sendBroadcast()返回后检查权限,因为系统尝试将提交的广播传递给给定的接收者。因此,权限失败不会导致异常被抛回调用者;它不会传达意图。以同样的方式,可以向Context.registerReceiver()提供权限,以控制谁可以向编写的广播接收者。另一方面,在调用Context.sendBroadcast()时可以提供权限,以限制允许哪些BroadcastReceiver对象接收广播(见下文)。

ContentProvider权限(使用<provider>标记)限制谁可以访问ContentProvider中的数据。 (ContentProvider有一个重要的额外安全设施,称为URI权限,稍后将对此进行描述。)与其他组件不同,您可以设置两个单独的权限属性:android:readPermission限制谁可以从提供程序读取,以及android: writePermission限制谁可以写入它。请注意,如果提供程序受读取和写入权限保护,则仅保留写入权限并不意味着您可以从provider读取。首次检索提供程序时将检查权限(如果您没有权限,将抛出SecurityException),并且在提供程序上执行操作时。使用ContentResolver.query()需要保持读取权限;使用ContentResolver.insert(),ContentResolver.update(),ContentResolver.delete()需要写入权限。在所有这些情况下,未保留所需的权限会导致从调用中抛出SecurityException。

发送广播时执行权限

除了强制执行谁可以将Intent发送到已注册的BroadcastReceiver(如上所述)之外,您还可以在发送广播时指定所需的权限。通过使用权限字符串调用Context.sendBroadcast(),您需要接收方的应用程序必须拥有该权限才能接收您的广播。

请注意,接收方和广播发送者都需要权限。在这种情况下,必须通过两次权限检查才能将Intent传递给关联的目标。

其他权限

任何对服务的调用都可以强制执行任意细粒度的权限。这是通过Context.checkCallingPermission()方法完成的。使用所需的权限字符串进行调用,它将返回一个整数,指示是否已将该权限授予当前调用进程。请注意,这只能在您执行来自其他进程的调用时使用,通常是通过从服务发布的IDL接口或以其他方式执行到另一个进程。

还有许多其他有用的方法可以检查权限。如果您有另一个进程的pid,则可以使用Context方法Context.checkPermission(String,int,int)来检查针对该pid的权限。如果您具有另一个应用程序的包名称,则可以使用直接PackageManager方法PackageManager.checkPermission(String,String)来确定该特定包是否已被授予特定权限。

URI权限


当与content provider一起使用时,到目前为止讲述的标准权限系统往往还不够。content provider可能希望使用读取和写入权限来保护自己,而其直接客户端还需要将特定URI传递给其他应用程序以供其操作。典型示例是邮件应用程序中的附件。应该通过权限来保护对邮件的访问,因为这是敏感的用户数据。但是,如果图像附件的URI被提供给图像查看器,则该图像查看器将无权打开附件,因为它没有理由拥有访问所有电子邮件的权限。

此问题的解决方案是per-URI权限:启动活动或将结果返回给活动时,调用者可以设置Intent.FLAG_GRANT_READ_URI_PERMISSION和/或Intent.FLAG_GRANT_WRITE_URI_PERMISSION。这授予接收活动权限访问Intent中的特定数据URI,无论它是否具有访问与Intent对应的内容提供者中的数据的任何权限。

此机制允许一种通用的功能样式模型,其中用户交互(打开附件,从列表中选择联系人等)驱动临时授予细粒度权限。这可以是将应用程序所需权限减少到与其行为直接相关的权限的关键工具。

但是,授予细粒度URI权限需要与持有这些URI的content provider进行一些协商。强烈建议content provider实现此功能,并通过android:grantUriPermissions属性或<grant-uri-permissions>标签来声明他们。

可以在Context.grantUriPermission(),Context.revokeUriPermission()和Context.checkUriPermission()方法中找到更多信息。

猜你喜欢

转载自blog.csdn.net/weixin_42703445/article/details/82862385