Android是一个权限分离的操作系统,其中每个应用程序以不同的系统标识运行(Linux用户标识和组标识)。系统的某些部分也被分成不同的身份。因此,Linux将应用程序彼此隔离并与系统隔离。
通过“权限”机制提供额外的更细粒度的安全功能,该机制强制限制特定进程可以执行的特定操作,和per-URI权限,用于授予对特定数据的临时访问权限。
本文档描述了应用程序开发人员如何使用Android提供的安全功能。 Android Security Overview中提供了更为通用的Android安全概述。
Android安全体系结构的核心设计点是,默认情况下,任何应用程序都无权执行任何会对其他应用程序,操作系统或用户产生负面影响的操作。这包括读取或写入用户的私人数据(如联系人或电子邮件),读取或写入其他应用程序的文件,执行网络访问,保持设备唤醒等。
因为每个Android应用程序都在进程沙箱中运行,应用程序必须明确共享资源和数据。他们通过声明权限来使用普通沙箱未提供的功能。应用程序静态声明所需的权限,Android系统会提示用户同意。
应用程序沙箱不依赖于用于构建应用程序的技术。尤其是Dalvik VM不是安全边界,任何应用程序都可以运行原生代码(请参阅Android NDK)。所有类型的应用程序(Java,本机和混合应用程序)都以相同的方式进行沙盒化,并且具有相同的安全性。
所有APK(.apk文件)必须使用其私钥由其开发人员持有的证书进行签名。此证书标识应用程序的作者。证书不需要由证书颁发机构签名;对于Android应用程序来说,使用自签名证书是完全允许的,也是通用的方式。Android中证书的目的是区分应用程序作者。这允许系统授予或拒绝应用程序访问签名级权限,并授予或拒绝应用程序的请求与另一个应用程序具有相同的Linux身份。
在应用程序安装时,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的清单文件中包含一个或多个
例如,需要监视传入SMS消息的应用程序将指定:
...
如果您的应用在其清单中列出了正常权限(即,不会对用户的隐私或设备操作造成太大风险的权限),系统会自动授予这些权限。如果您的应用在其清单中列出了危险权限(即可能会影响用户隐私或设备正常运行的权限),系统会要求用户明确授予这些权限。Android发出请求的方式取决于系统版本以及应用所针对的系统版本:
通常,权限获取失败会导致将SecurityException抛回应用程序。但是,并不能保证任何情况下都能发生。例如,sendBroadcast(Intent)方法在方法调用返回后将数据传递给每个接收者时检查权限,如果存在权限失败,您将不会收到异常。但是,在大多数情况下,都会在系统日志中打印权限失败。
可以在Manifest.permission中找到Android系统提供的权限。任何应用程序也可以定义和强制执行自己的权限,因此并不是所有的权限列出来了。
在程序运行期间,可以在许多地方强制执行特定权限:
随着时间的推移,可能会向平台添加新的限制,以便为了使用某些API,您的应用必须请求以前不需要的权限。假设已有的应用程序可以正常访问这些API,因此Android可以将新的权限请求应用于应用程序的清单,以避免在新平台版本上破坏应用程序。Android根据为targetSdkVersion属性提供的值,决定应用是否可能需要该权限。如果该值低于添加权限的版本,则Android会添加权限。
例如,在API级别4中添加了WRITE_EXTERNAL_STORAGE权限,以限制对共享存储空间的访问。如果您的targetSdkVersion为3或更低,则此权限会在较新版本的Android上添加到您的应用中。
警告:如果您的应用自动添加了权限,则Google Play上的应用列表会列出这些附加权限,即使您的应用可能实际上并不需要这些权限。
要避免这种情况并删除您不需要的默认权限,请始终将targetSdkVersion更新为尽可能高。您可以在Build.VERSION_CODES
文档中查看每个版本添加了哪些权限。
系统权限分为几个保护级别。要了解的两个最重要的保护级别是正常和危险的权限:
所有危险的Android系统权限都属于权限组。如果设备运行的是Android 6.0(API级别23)且应用的targetSdkVersion为23或更高,则当您的应用请求危险权限时,以下系统行为适用:
任何权限都属于权限组,包括应用程序定义的普通权限和权限。但是,只有危险权限影响用户体验。使用正常权限时,您可以忽略权限组。
如果设备运行的是Android 5.1(API级别22)或更低版本,或者应用程序的targetSdkVersion为22或更低,系统会要求用户在安装时授予权限。系统仅仅告诉用户应用程序需要哪些权限组,而不是具体某个权限。
表1.危险权限和权限组。
要强制执行您自己的权限,您必须首先在AndroidManifest.xml中使用一个或多个
例如,应用程序想要控制谁可以启动其某个activity可以声明此操作的权限,如下所示:
...
注意:除非所有软件包都使用相同的证书进行签名,否则系统不允许多个软件包声明具有相同名称的权限。系统不允许用户安装具有相同权限名称的其他包,除非这些包使用与第一个包相同的证书进行签名。为避免命名冲突,我们建议对自定义权限使用反向域式命名,例如com.example.myapp.ENGAGE_HYPERSPACE。
必须使用protectionLevel属性,告诉系统如何通知用户需要许可的应用程序,或允许谁持有该权限,如链接文档中所述。android:permissionGroup属性是可选的,仅用于帮助系统向用户显示权限。在大多数情况下,您可以将其设置为标准系统组(在android.Manifest.permission_group中列出),尽管您可以自己定义一个组。最好使用现有组,因为这简化了向用户显示的权限请求UI。
您需要提供权限的标签和说明。这些是字符串资源,用户在查看权限列表(android:label)或单个权限的详细信息(android:description)时可以看到。标签应该很简短;仅仅描述权限保护的关键功能的几个词。描述应该是一些描述许可允许持有人做什么的句子。我们的约定是两句话描述:第一句描述权限,第二句警告用户如果申请被授予许可可能受影响的事物类型。
以下是CALL_PHONE权限的标签和说明示例:
directly call phone numbers
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.
您可以使用“设置”应用程序和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
...
通过定义
您可以通过在AndroidManifest.xml中使用高级权限来限制访问系统或应用程序的整个组件。为此,请在所需组件上包含android:permission属性,命名控制对其进行访问的权限。
Activity权限(使用
Service权限(使用
BroadcastReceiver权限(使用
ContentProvider权限(使用
除了强制执行谁可以将Intent发送到已注册的BroadcastReceiver(如上所述)之外,您还可以在发送广播时指定所需的权限。通过使用权限字符串调用Context.sendBroadcast(),您需要接收方的应用程序必须拥有该权限才能接收您的广播。
请注意,接收方和广播发送者都需要权限。在这种情况下,必须通过两次权限检查才能将Intent传递给关联的目标。
任何对服务的调用都可以强制执行任意细粒度的权限。这是通过Context.checkCallingPermission()方法完成的。使用所需的权限字符串进行调用,它将返回一个整数,指示是否已将该权限授予当前调用进程。请注意,这只能在您执行来自其他进程的调用时使用,通常是通过从服务发布的IDL接口或以其他方式执行到另一个进程。
还有许多其他有用的方法可以检查权限。如果您有另一个进程的pid,则可以使用Context方法Context.checkPermission(String,int,int)来检查针对该pid的权限。如果您具有另一个应用程序的包名称,则可以使用直接PackageManager方法PackageManager.checkPermission(String,String)来确定该特定包是否已被授予特定权限。
当与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属性或
可以在Context.grantUriPermission(),Context.revokeUriPermission()和Context.checkUriPermission()方法中找到更多信息。