System Permissions
英文原文:http://developer.android.com/guide/topics/security/permissions.html
采集日期:2014-5-7
搬迁自原博客:http://blog.sina.com.cn/s/blog_48d491300101hcbh.html
Android 是权限隔离的操作系统,每个应用程序都以唯一的系统标识符(Linux 用户 ID 和组 ID)运行。 系统的某些部分也由唯一的标识符相分隔。 因此,Linux 可以将应用程序相互隔离,也与系统隔离。
更精细的安全措施则由“权限”机制来提供,这种机制可以对某个进程可执行的特定操作进行限制, 以及由 URI 前缀机制对点对点访问某部分数据进行授权。
本文描述了开发人员使用 Android 安全特性的方法。 更为宏观的 Android 安全性概述 在 Android Open Source Project 中给出描述。
安全架构
设计 Android 安全架构时有一条核心思想,就是默认情况下所有应用程序都无权执行妨碍其他应用程序、操作系统或用户的任何操作。 这些操作包括:读写用户私有数据(比如通讯录或 email)、读写其他应用程序的文件、访问网络、保持设备唤醒状态等等。
因为每个 Android 应用程序都是在各自的进程沙盒中运行,所以必须公开需共享的资源和数据。 为了获得这些不由基本沙盒提供的额外功能,需通过声明所需的permissions来实现。 应用程序首先要静态声明所需的权限,然后 Android 系统会在安装时交由用户许可。 Android 不提供动态授权机制(运行时授权),因为这会让用户体验变得更复杂,不利于系统的安全。
应用程序沙盒不依赖于任何编译应用程序所用到的技术。 特别是 Dalvik VM 的边界并不安全,任何应用程序都可以运行本地原生(native)代码(参阅 Android NDK)。 所有的应用程序— Java、原生或混合代码 — 都以同样的方式运行于沙盒中,每个程序的安全等级都是同等的。
应用程序签名
所有的 APK (.apk
文件)都必须经由某个证书签名。
该证书的私钥由开发者持有,标识了应用程序的作者。
证书不需要经过认证机构签发,它拥有完全许可,典型的用途是用于应用程序自身签名认证。 使用 Android
证书的目的是为了区分应用程序的作者。 利用证书,系统可以允许或拒绝应用程序访问 签名级别的权限
或 申请与其他应用程序相同的
Linux ID
用户 ID 和文件访问
在安装时, Android 会为每个程序包分配一个唯一的 Linux 用户 ID 。 在当前设备该程序包的生存期内,这个 ID 维持不变。 在不同的设备上,同一个包可能会拥有不同的 UID,但重要的是每个设备上的包 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 应用程序默认是没有任何权限的。 这就是说,它不能做任何妨碍用户体验的事,也不能访问设备上的任何数据。
为了使用受保护的设备功能,你必须在AndroidManifest.xml
中包含一个或多个
标签,以便声明所需的权限。
例如,如果应用程序需要监视 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>
在安装时,根据对程序签名的检查情况和用户对所声明权限的反馈,安装工具将会把申请的权限授予应用程序。 在应用程序运行时,不会再要求用户对权限进行校验。 要么是在安装时已被赋予某个权限,应用程序可以按照预期使用相应的功能; 要么就是没有被授权,使用这些功能时就会直接失败,且不会给用户任何提示。
通常授权失败会导致向应用程序抛出 SecurityException
。 但是这并不是每次都会发生。比如,在向每个接收器分发数据时 sendBroadcast(Intent)
方法会检查权限,但这时调用已经返回了,因此你就无法收到授权失败的异常。 不过,几乎所有情况下,授权失败都会记入系统日志。
正常情况下(比如从 Google Play Store 安装应用程序时),只要用户不同意任一权限请求,应用程序就不会安装。 因此,通常你不必担心授权不足导致的运行失败,因为只要安装成功就表示应用程序已经获得了足够的授权。
Android 系统可提供的权限都在 Manifest.permission
中给出了。 应用程序还可以定义并启用自定义的权限,因此这份清单无法列举所有的权限。
权限控制可以在程序运行过程中的很多场合生效:
- 执行系统调用时,阻止应用程序执行某些操作。
- 启动 Activity 时,阻止应用程序打开其他应用程序的 Activity。
- 发送和接收广播时,控制谁可接收你的广播,或谁可向你发送广播。
- 访问和操作 Content Provider 时。
- 绑定或启动服务时。
提醒: 随着时间的推移,系统可能会对某些 API 的使用新增一些限制,你的应用程序必须申请一些以前不收限制的权限。 因为已有的应用程序认为这些 API 应是可以自由访问的,为了避免在新版本系统中的运行中止, Android 可能会在这些应用程序的 manifest 中加入新权限的申请。 根据 targetSdkVersion
属性值,Android 可以确定应用程序是否可能需要这些新权限。 如果该属性值低于引入某权限时的版本,那么 Android 就会加入这个权限。
例如, WRITE_EXTERNAL_STORAGE
权限是从 API 级别 4 开始加入的,用于访问共享存储。 如果你的 targetSdkVersion
是 3 以下,则在更高版本的 Android 中会把该权限申请加入应用程序中。
请注意,如果执行了权限的添加工作,在 Google Play 中就会显示你的应用程序需要这些权限,即使应用程序实际上不需要用到。
为了避免这一点并去除不需要的默认权限,请把 targetSdkVersion
维持更新为尽可能高的版本。 关于每个版本加入的权限,可以参阅文档 Build.VERSION_CODES
。
声明和应用权限
为了应用自定义的权限,首先必须在AndroidManifest.xml
中声明 一个或多个 < permission >
标签。
例如,某个应用程序需要对可以启动其 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>
< protectionLevel >
属性是必填项,这会告知系统用户如何获知应用程序需要的权限,或者谁可以拥有这个权限,在链接文档里有详细说明。
< 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 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>
通过“设置”应用和命令 adb shell pm list permissions
,你可以查看当前系统中已定义的权限。 在Settings > Applications 中可以打开“设置”应用。 选中一个应用程序,打开下拉项可以查看它所用到的权限。 对于开发人员而言,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 中应用权限
高级权限可以通过AndroidManifest.xml
实现控制,这些权限可以限制对系统级、应用级的全局性组件的访问。 这些权限的请求都包含在相应组件的 android:permission
属性中,访问控制通过权限的名称来识别。
Activity
权限(应用于标签 < activity >
)限制了谁可以启动该 Activity。在 Context.startActivity()
和 Activity.startActivityForResult()
时将检查此权限。如果调用者没有请求此权限,则会在调用时抛出 SecurityException
。
Service
权限(应用于标签 < service >
)限制了谁可以启动或绑定该服务。在 Context.startService()
、 Context.stopService()
和 Context.bindService()
时,将会检查此权限。如果调用者没有请求该权限,则会在调用时抛出 SecurityException
。
BroadcastReceiver
权限(应用于标签 < receiver >
)限制了谁可以向该接收器发送广播。在 Context.sendBroadcast()
返回之后,系统会尝试把已提交的广播向给定的接收器分发,这时将会检查此权限。 因此,授权失败会向调用者抛出异常,Intent 也就不会被分发。 同样,可以在 code>Context.registerReceiver() 上应用权限,用于控制谁可以向程序动态注册的接收器发送广播。 再换一种方式,在调用 Context.sendBroadcast()
时也可以应用权限控制,用于限制哪些 BroadcastReceiver 对象可以接收广播(详见下文)。
ContentProvider
权限(应用于标签 < provider >
)限制了谁可以访问 ContentProvider
中的数据。(Content Provider 还拥有一个重要的安全特性,叫做URI permissions,这稍后会讨论。) 与其他组件不同,这里可以分别设置两个独立的属性: android:readPermission
限制谁可以从该 Provider 中读取数据,而 android:writePermission
限制谁可以写入数据。 请注意,如果 Provider 同时受到读和写权限的保护,那么拥有写权限并不意味着可以从中读取数据。 首先在获取 Provider 时将会检查该权限(如果两个权限都没有,将会抛出 SecurityException), 在执行读写操作时也会进行检查。使用 ContentResolver.query()
时需要拥有读取权限,使用 ContentResolver.insert()
、 ContentResolver.update()
、 ContentResolver.delete()
时则需要写入权限。 在上述场合,未获得授权将会在调用时抛出 SecurityException
。
发送广播时应用权限
除了能够限制谁可以向已注册的 BroadcastReceiver
发送 Intent 之外(如上所述),你还可以在发送广播时设定一个权限。 通过在调用 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 发送给图片浏览程序,图片浏览程序是无权打开这些附件的,因为它没有访问所有 e-mail 的权限。
解决这一问题的办法就是使用 URI 前缀权限:在启动 Activity 或向 Activity 返回结果时,调用者可以设置 Intent.FLAG_GRANT_READ_URI_PERMISSION
和/或 Intent.FLAG_GRANT_WRITE_URI_PERMISSION
。 这样就会给接收方 Activity 赋予访问 Intent 里指定 URI 数据的权限,并与是否有权访问 Intent 对应 Content Provider 中的数据无关。
上述机制实现了一种通用的功能性授权模式,即由用户交互操作(打开附件、从通讯录中选择联系人等)驱动的点对点精细授权。 在 Android 平台中,应用程序需要考虑的权限如此之少,恐怕要归功与此,只需要那些与程序行为直接相关的权限就够用了。
不过,精细化的 URI 授权需要对应 Content Provider 的合作才能生效。 强烈建议 Content Provider 实现这种授权功能,并通过 android:grantUriPermissions
属性或 标签进行声明
更多信息请参阅 Context.grantUriPermission()
、 Context.revokeUriPermission()
和 Context.checkUriPermission()
方法。
还可能感兴趣:
- 设备兼容性
- 介绍了各种设备上运行的 Android,以及如何针对每种设备优化应用程序、如何根据设备的不同限制应用程序的运行。
- Android 安全性概述
- 详细讨论了 Android 平台的安全模型。