Android是一种特权分隔的操作系统,在Android上运行的每个应用程序都具有各自独立的系统标识(Linux用户ID和组ID)。 系统各部分有不同的身份标识。因此,Linux上运行的各个应用程序相互独立且与系统无关。
Android的“权限许可”机制通过限定特定的进程能够执行的指定操作和限定对每一个资源点对点的访问的URI许可来提供附加细粒度的安全功能。
本文档介绍了应用程序开发人员如何使用由Android提供的安全功能。 在Android 开放源代码项目AOSP(Android Open Source Project)中提供了更一般的Android安全性概述。
Android安全体系架构设计的核心是在默认情况下没有任何一个程序可以执行对其他程序、操作系统或者用户有害的操作,包括读写用户的隐私数据(例如联系人或者电子邮件),读写其他程序的文件,进行网络访问或者唤醒设备等等。
由于内核让每个应用程序运行在独立的沙盒中,应用程序必须通过声明所需要而沙盒没有提供的权限来明确的分配资源和数据。Android没有采用会使用户体验复杂并且不利于安全的动态授权机制。应用程序静态的声明他们所需要的权限,在程序安装时Android系统会提示用户同意它们获取这些权限。
沙盒程序独立于生成普通应用程序的机制。特别地,Dalvik虚拟机不是一个安全的边界,任何的应用程序都能够运行本地代码(参照Android NDK)。所有类型的应用程序——java、native和混合的——均用相同的方式置以相同的安全等级在沙盒中运行。
所有的Android应用程序(apk文件)都必须使用一个开发人员掌握私钥、用于识别应用程序作者的证书进行签名。该证书要求很宽松,并不需要由专门的证书颁发机构进行签名,Android应用程序可以使用自签名的证书。Android证书的目的是区分应用程序的作者,可以允许操作系统授予或者拒绝应用程序使用签名级别的权限和操作系统授予或者拒绝应用程序请求和其他应用程序相同的Linux身份。
在安装的时候,Android会给每个程序分配一个不同的Linux用户身份(UID)。软件在设备上的生命周期中这个身份标识保持恒定不变。在不同的设备上,相同的软件可能会有一个不同的UID;重要的是在给定的设备上不同的包是不同的UID。
因为安全是在进程级别上实现的,两个软件包的代码在同一个进程中不能够同时正常运行,他们必须以不同的Linux用户运行。可以在每个程序包的AndroidManifest.xml中将manifest标签的shareUserId属性分配相同的用户ID,把两个应用程序看作拥有同样的用户ID和文件权限的同一个应用程序。为了保持安全,只有具有相同签名(请求的sharedUserId也相同)的应用程序才会分配相同的用户ID。
任何由应用程序存储的数据将被赋予应用程序的用户ID,正常情况不能被其它应用程序访问。当使用getSharedPreferences(String, int), openFileOutput(String, int), 或openOrCreateDatabase(String, int, SQLiteDatabase.CursorFactory)创建一个新的文件时,可以使用MODE_WORLD_READABLE或MODE_WORLD_WRITEABLE标记允许其他应用程序来读/写文件。设置这些全局的读写权限标记后,该文件仍然为创建文件的应用程序所拥有,任何其他应用程序可以看到它。
一个基本的android程序是没有任何权限的。也就是说,无论是从用户体验上和设备数据上都没有什么危害。在产品需求下,为了能够使用设备的受保护特性,你必须在AndroidManifest.xml里声明至少一种所需要的权限。
例如,一个程序需要管理收到的短信,需要指定:
<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可以请求的方式取决于系统版本,并通过您的应用程序有针对性的系统版本:
很多时候,一个权限失败将导致SecurityException异常被抛回给应用程序。但是,这不能保证到处发生。例如,sendBroadcast(Intent)方法检查的权限,数据被传递到每个接收器,该方法调用返回之后,因此,如果有权限的失败,你将不会收到异常。在几乎所有的情况下,然而,一个许可失败将被打印到系统日志。
通过Android系统提供的权限可以在这里找到Manifest.permission。任何应用程序还可以定义并执行它自己的权限,所以这不是所有可能的权限的完整列表。
特定权限可以在一些程序的运行过程中的地方被执行:
Android 的版本变更还是比较快的,在不同的版本上,对一些权限做出的一定的修改,为了避免因为系统升级给app 带来的问题,Android 提供了一个自动调整的机制。比如WRITE_EXTERNAL_STORAGE权限在API leve 4 的时候加入,如果你的targetSdkVersion是3 或者更低的话,这个权限你自动就获取了,不过在安装的时候,这个权限会提示给用户的,即使你没有申请。
Google 建议targetSdkVersion尽量使用最高的版本。
系统权限被分成几个不同的保护等级,其中有两个重要的等级是普通和危险权限。
所有危险Android系统权限属于权限组。如果设备运行的是Android 6.0(API级别23)和应用程序的targetSdkVersion为23或更高,当你的应用程序请求一个危险的权限以下系统行为适用:
如果设备运行的是Android 5.1(API级别22)以下,或应用程序的 targetSdkVersion是22或更低时,系统会要求用户授予在安装时的权限。再次,系统只是告诉用户什么权限组的应用需求,而不是个人的权限。
表1.危险权限和权限组。
Permission Group | Permissions |
---|---|
CALENDAR |
|
CAMERA |
|
CONTACTS |
|
LOCATION |
|
MICROPHONE |
|
PHONE |
|
SENSORS |
|
SMS |
|
STORAGE |
|
若打算自定义权限,您需要在manifest文件中使用标签声明。
比如说,若打算控制某个Activity的启动权(谁可以启动该Activity),您可以配置如下代码:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.me.app.myapp" >
<permission android:name="com.me.app.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>
其中
若您在manifest文件中声明了高级权限(High-level permissions),那么这种权限不是所有组件都能获取的,若希望针对某个组件获取高级权限,您应当在该组件的标签中用android:permission 属性声明该高级权限。
除了之前说过的权限(用于限制谁可以发送广播给相应的BroadcastReceiver),还可以在发送广播的时候指定一个许可。在调用Context.sendBroadcast()的时候使用一个permission string,你就可以要求接收器的宿主程序必须有相应的权限。
值得注意的是接收器和广播都可以要求许可。当这种情况发生时,这两种permission检查都需要通过后才会将相应的intent发送给相关的目的地。
在调用service的过程中可以设置更加细化的许可。这是通过Context.checkCallingPermission()方法来完成的。调用的时候使用一个想得到的permission string,返回给调用方一个整数判断是否具有相关权限。需要注意的是这种情况只能发生在来自另一个进程的调用,通常是一个service发布的IDL接口或者是其他方式提供给其他的进程。
Android提供了很多其他有效的方法用于检查许可。如果有另一个进程的pid,可以通过Context.checkPermission(String, int, int)去检查该进程的权限设置。如果有另一个应用程序的包名,可以直接用PackageManager.checkPermission(String, String)来确定该包是否已经拥有了相应的权限。
到目前为止我们讨论的标准的permission系统对于内容提供者(content provider)来说是不够的。一个内容提供者可能想保护它的读写权限,而同时与它对应的直属客户端也需要将特定的URI传递给其它应用程序以便对该URI进行操作。一个典型的例子是邮件应用程序的附件。访问邮件需要使用permission来保护,因为这些是敏感的用户数据。然而,如果有一个指向图片附件的URI需要传递给图片浏览器,那个图片浏览器是不会有访问附件的权利的,因为它不可能拥有所有的邮件的访问权限。
针对这个问题的解决方案就是per-URI permission:当启动一个activity或者给一个activity返回结果的时候,调用方可以设置Intent.FLAG_GRANT_READ_URI_PERMISSION和/或Intent.FLAG_GRANT_WRITE_URI_PERMISSION。这赋予接收活动(activity)访问该意图(Intent)指定的URI的权限,而不论它是否有权限进入该意图对应的内容提供者。
这种机制允许一个通常的能力-风格(capability-style)模型,以用户交互(如打开一个附件, 从列表中选择一个联系人)来驱动细化的特别授权。这是实现减少应用程序所需要的权限而只留下和程序行为直接相关的权限时很关键的一步。
这些URI permission的获取需要内容提供者(包含那些URI)的配合。强烈建议在内容提供者中实现这种功能,并通过android:grantUriPermissions或者grant-uri-permissions标签来声明支持。
更多的信息可以参考Context.grantUriPermission(), Context.revokeUriPermission()和Context.checkUriPermission()函数。