- 一、系统权限
- 1.1 安全机制
- 1.2 应用签名
- 1.3 用户ID和文件访问
- 1.4 权限使用方法
- 1.5 声明权限和权限控制
- 1.5.1 在manifest文件中的权限限制
- 1.5.2 发送广播时的权限限制
- 1.5.3 其他权限限制
- 1.6 URI权限
一、系统权限
Android操作系统的权限是分开的,每个应用在运行时都伴随着一个系统ID(Linux用户ID和组ID)。系统的部分功能也被拆分到ID中。Linux因此将应用跟其他应用和系统隔离开。
Android通过附加的安全特性:“权限”机制,限制某个进程可以进行的特殊操作,用URI权限限制其访问特殊的数据。
1.1 安全机制
Android安全机制的中心思想是,默认情况下,应用没有权限进行可能影响其他应用、操作系统或用户的操作,如读写用户私有数据(如通讯录或email)、读写其他应用的文件、访问网络、保持设备唤醒等等。
由于每个Android应用都运行在进程沙箱中,所以应用必须指明其需要哪些资源和数据。应用可以通过声明基础沙箱不提供的功能的权限来做到这一点。应用预先声明它们需要的权限,在应用安装时,Android系统会提示用户并由用户选择是否允许应用获取这些权限。
应用沙箱不依赖于构建应用的技术。Dalvik虚拟机不是安全边界,任何应用都可以运行native代码(原句:In particular the Dalvik VM is not a security boundary, and any app can run native code)。所有类型的应用:java、native或混合型,都是同样的方式运行在沙箱中,并且有相同的权限机制。
1.2 应用签名
所有的APK都必须用证书签名(证书的私钥保存在开发者那里)。证书用来标识应用的作者。证书不需要权威证书机构的认证,它可以是而且通常情况下是自签名的证书。Android使用证书的目的是区分应用作者,这样的话系统就可以确定是否给此应用访问某个其他应用签名级操作的权限,还有是否给予此应用相同的Linux ID。
1.3 用户ID和文件访问
Android系统会在安装应用时为它分配一个Linux用户ID。这个ID在应用安装在设备上后是不变的。在不同的设备上,相同应用可能有不同的用户ID;在同一设备上,每个应用的用户ID都是不同的。
由于进程级别的安全策略,任意两个应用的代码不能运行在同一个进程上,它们作为不同的Linux用户运行。你可以在manifest文件的manifest标签指定sharedUserId属性,要求系统分配相同的用户ID。然后,这两个应用将被当做同一个应用,使用相同的用户ID,并有相同的文件权限。注意,为了保证安全,签名相同且sharedUserId相同的两个应用才会被分配相同的用户ID。
系统会按照应用的用户ID保存应用保存的数据,从而使其他应用不能轻易的访问这些数据。当使用getSharedPreferences(Stirng,int)、openFileOutput(String,int)、openOrCreateDatabase(Sting,int,SQLiteDatabase.CursorFactory)创建文件时,你可以用MODE_WORLD_READABLE或MODE_WORLD_WRITEABLE这样的flag允许其他应用读写你的文件。设置了这些flag后,这些文件仍然由你的应用拥有,但是它们的全局读写权限会发生变化,这样其他应用就可以访问它们了。
1.4 权限使用方法
默认情况下Android应用没有任何权限,这意味着应用不能做任何能够影响用户体验或应用数据的操作。为了能够使用设备的一些特性,你必须在manifest文件中添加相应的<uses-permission>
。
在应用安装时,package installer会根据应用的签名检查应用申请的权限,并向用户显示你需要的权限及可能的后果,由用户决定是否给予你权限。在用户使用过程中不会去检查权限,也就是说要么在安装的时候就批准该权限,使应用可以使用对应特性;要么就不批准,这样应用任何对此特性的操作都会失败,而且不会提示给用户。
通常权限问题会以SecurityException的形式返回给你的应用。然而,不能保证权限问题总是这样提示你。例如,用sendBroadcast(Intent)方法会检查每个receiver的权限,这是发生在方法返回之后的,所以当权限问题发生时你不会捕获到异常。大多数情况下,权限问题会打印到系统log中。
通常情况下,如果用户不允许你申请的权限,你的应用会安装失败。所以你不需要担心由于用户不给权限而导致运行时异常。
Android系统提供的权限可以在Manifest.permission找到。但是应用也可以定义自己的权限,所以Manifest.permission不能包含所有的权限。
在程序运行时,有以下场景会强制进行权限检查:
- 防止应用调用某些系统功能
- 防止应用启动其他应用的activity
- 控制谁可以接收你的广播,控制谁可以向你发送广播
- content provider操作
- 绑定或start服务
注意:系统平台更新后,可能某些API以前不需要权限,而现在要了。由于要保证存量应用能正确执行,Android会自动在应用manifest中添加权限申请。Android系统是根据应用的targetSdkVersion属性确定需要添加哪些权限的。如果这个值低于当前版本,那么Android系统就会添加新增的权限。
例如,WRITE_EXTERNAL_STORAGE权限是在API 4添加的,以限制对于共享存储空间的访问。如果你的targetSdkVersion是3或更低,那么这个权限会自动的添加到manifest中。
所以需要小心,Google应用商店在显示应用所需权限时,会显示一些你的应用可能没有申请的权限。
为了避免这种情况,你需要将你的targetSdkVersion更新到尽可能高。
1.5 声明权限和权限控制
在manifest文件中添加<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)或自定义的组。尽量使用已经存在的组,以简化向用户显示的权限信息。
<label>
和<description>
属性是string类型,用于向用户展示权限的详细信息。label应该尽量简短,简要描述这个权限保护的功能是什么即可。description适当可以长些,描述这个选项可以让权限拥有者做什么事情。我们提倡用两行文字表示description,一行描述权限,另一行警告用户如果获取了这个权限可能发生什么坏事情。
你可以通过系统设置或shell命令“adb shell pm list permissions”查看当前系统存在的权限。对于开发者,在adb命令中添加“-s”选项可以查看权限详细信息。
1.5.1 在manifest文件中的权限限制
限制系统组件访问或应用访问的高等级权限可以在manifest文件中申请。
Activity权限限制了谁可以启动此activity。这权限在调用Context.startActivity()或Activity.startActivityForResult()方法时进行检查,如果调用方没有申请此权限,方法调用会抛出SecurityException。
Service权限限制了谁可以bind此service。这权限在调用Context.startService()、Context.stopService()、Context.bindService时进行检查,如果调用方没有申请此权限,方法调用会抛出SecurityException。
BroadcastReceiver权限限制了谁可以向这个receiver发送广播。这个权限在调用Context.sendBroadcast()方法并返回后检查,所以即使此receiver没有权限,也不会向调用方抛出异常,它只是跳过这个receiver。同样的,Context.registerReceiver()方法会控制谁可以向此注册的receiver发送广播。还有一种形式是,调用Context.sendBroadcast()时限制哪个BroadcastReceiver对象可以接收广播(详细过程见下文)。
ContentProvider权限限制了谁可以访问ContentProvider中的数据。
ContentProvider有一个额外的安全方案,叫做URI权限,后面会讲到。不像其他组件,ContentProvider有两个跟权限有关的属性:“android:readPermission”限制了谁可以从这个provider中读数据,“android:writePermission”限制了谁可以向它写数据。注意,如果某个provider被读写权限同时保护,那么你拥有写权限不意味着你可以从provider读数据。这个权限在我们第一次搜索provider时进行检查(如果你读写权限都没有,会触发SecurityException)。使用ContentResolver.query()要求读权限,ContentResolver.insert()、ContentResolver.update()、ContentResolver.delete()要求写权限。
1.5.2 发送广播时的权限限制
为了限制谁可以向某个已注册的BroadcastReceiver发送intent,你可以指定在发送广播时需要的权限。通过在调用Context.sendBroadcast()时添加表示权限的string,就可以限制receiver所在应用必须有这个权限才能收到你的广播。
注意receiver和broadcast都可以声明权限要求,这时必须两边的权限检查都通过了才能正确收到广播。
1.5.3 其他权限限制
如果想要对service的调用进行更细致的检查,可以调用Context.checkCallingPermission()方法检查调用方进程是否有指定权限。注意,这个方法只针对从其他进程的调用,通常是通过某个服务暴露的IDL接口的调用(或者其他跨进程调用)。
如果你有其他进程的pid,你可以使用Context.checkPermission(String,int,int)检查此进程是否有指定权限。如果你有其他应用的包名,可以使用PackageManager.checkPermissin(String,String)检查这个应用是否拥有某个权限。
1.6 URI权限
标准权限系统通常对于content provider是不够用的。content provider希望保护自己的读写权限,但是它也得向调用方提供特定的URI。以邮箱应用的附件功能为例。访问邮箱需要权限,因为这是敏感的用户数据,然而,如果有个图片浏览器需要打开标识图片附件的URI,将会提示没有相关权限。
要解决这个问题,Android引入了pre-URI权限机制:当启动某个activity或者从某个activity返回结果时,调用方可以设置Intent.FLAG_GRANT_READ_URI_PERMISSION或Intent.FLAG_GRANT_WRITE_URI_PERMISSION,这个flag可以给予被调用的activity访问intent中的特殊数据URI的权限,而不管被调用的activity本身是否有权限访问这些数据。
这个机制提供了一个给予操作临时权限(如打开附件、从列表中选择通讯录等)的通用的能力-类型模型。它可以减少应用需要的权限,让应用可以只申请跟自己行为相关的权限。
获取URI权限的前提是跟持有这些URI的content provider交互,这就要求content provider实现了这个机制(通过android:grantUriPermissions属性或者<grant-uri-permissions>
标签声明它支持这个机制)
更多信息请看Context.grantUriPermission()、Context.revokeUriPermission()、Context.checkUriPermission()方法。