Android系统构造在Linux系统之上,因此它采用了和Linux类型的权限隔离机制,也就是说,每个应用使用独立的系统标识(组标识加用户标识)来运行。部分系统应用也使用某个系统标识独立开来。底层的Liunx系统从而使得不同应用之间以及应用和系统之间隔离开来。
Android通过Permission(权限)机制进一步强化系统安全,通过Permssion来限制某个进程可以执行的操作,为访问某些特定的数据根据其URIPermissions赋予相应权限。
系统安全构架
Android系统安全构造的一个中心设计思想是:缺省情况下,应用无权执行可能对其它应用,操作系统或是用户造成影响的任何操作,这些操作可能包括读写用户个人数据(比如通讯录或是电子邮件),读写其它应用的文件,访问网络以及保持设置处于“唤醒”状态等。
Android系统使用“沙箱”将多个应用分隔开来,因此如有需要,应用必须明确指明需要和其它应用共享的资源或数据。这需要通过申明缺省“沙箱”未支持的“Permissions”来获得这些额外的功能。应用在Manifest文件中静态声明所需权限,Android系统在安装应用时会询问用户是否赋予该应用所需权限。为避免用户决定安全问题的复杂性,Android系统不支持在运行时动态赋予应用所需权限。
Android的“沙箱”机制独立于用来创建应用所使用的技术,尤其要注意的Dalvik虚拟机不是安全机制的界限,任意应用都可以运行Native代码。所有的应用,Java,Native或者是二者的混合,使用同样的“沙箱”安全机制并具有同等的安全等级。
应用签名
所有的Android应用(.apk文件)都必须使用安全证书来签名,证书的密钥由开发人员拥有。证书表明应用的开发人员身份。但这个安全证书并不要求是由某个公认的证书管理中心(CA)来签发。通常开发人员多使用自己签发的证书。Android使用证书的目的是为了用来区分应用的作者。这使得Android可以据此来赋予或拒绝应用所需的权限,或者拒绝或者赋予某个应用申请使用其它应用同样的Linux用户来运行该应用的请求。
用户标识和文件访问
在安装应用时,Android为每个安装包分配不同的Linux用户标识。这个标识在该安装包在设备上整个生命周期时保持一致。在不同的设备上,这个用户标志可能不同,重要的是在同一设备上,每个安装包使用不同的用户标识。
由于Android安全机制是基于进程来赋予的,通常情况下两个不同的应用程序包使用不的进程来运行,这是因为它们使用了不同的Linux用户来运行的。你可以通过在AndroidManifest.xml文件中通过sharedUserId属性为不同的安装包指明同一个用户标识,在这种情况下,这两个程序包运行在同一进程中,因此使用同样的安全机制。要注意的这两个安装包必须是使用同一个安装证书来签名,这样才能保证足够的安全机制。
应用程序保存的数据都会赋予应用使用的用户标识,通常情况下其它应用无法访问这些数据。在使用getSharedPreference(String,int), openFileOutput(String,int) 或openOrCreateDatabase(String, int, SQLiteDatabase.CursorFactory) 创建文件时,你可以通过设置MODE_WORLD_READABLE 或MODE_WORLD_WRITEABLE 标志允许其它应用来读写这些文件。设置了这些标志,这些文件的所有者仍然是应用的所有者,但正确打开了全局的读写权限,其它应用因此可以访问这些文件。
使用Permission
缺省情况下一个基本的Android应用不含有任何Permission定义,也就是说该应用无权执行一些可能对用户或者设备上数据造成不良影响的操作。为了访问设备的某些受限的功能,你必须在AndroidManifest.xml中定义一个或多个<uses-permission>标记来申明应用中所需的权限。
比如,一个需要监视接收到的短信的应用可以定义如下权限:
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.app.myapp" >
- <uses-permission android:name="android.permission.RECEIVE_SMS" />
- ...
- </manifest>
在安装应用时,应用所需的权限由应用安装程序来赋予应用,这将根据应用证书签名并由用户来决定。而在应用运行时无需用户再检查权限。要么是在安装时赋予应用使用某些功能对应的权限,之后应用可以任意使用该功能,要么是安装时拒绝赋予了某些权限,之后应用在使用这些功能时会失败而不会提示用户。
通常情况下,在权限失败时会导致抛出SecurtyException异常,但系统不保证总是会这样。比如,方法sendBroadcast(Intent)在就数据发送到每个Receiver过程中会检查所需权限,在方法结束后,如果有权限失败,你不会接收到异常。而在几乎所有情况下,所有的权限失败都会在写到系统日志中。
Android系统支持的所有Permission都可以在包Manifest.permission中找到。应用程序也可以自己定义和强化权限。
在应用运行时,某个特定的安全权限可以在下面几种情况下强化执行:
· 执行某个系统调用时,以阻止应用执行某个特定的功能。
· 启动某个Activity时,以阻止应用启动其它应用中的Activity。
· 发送和接受广播消息时,来控制谁可以发送广播消息,谁可以接收广播消息。
· 访问某个ContentProvider时。
· 绑定到某个Service时。
申明和执行权限
为了能够使用自定义的权限,你必须使用<permission>标记在AndroidManifest.xml文件中申明。
比如,如果一个应用需要控制哪个别的应用能够启动它定义的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>是必须的,它告诉操作系统如何通知用户应用需要哪些权限,或者谁能够使用这个权限。
属性<permssionGroup>是可选的,系统主要用它来帮助显示权限给用户。你通常可以把这个属性设置成标准系统组,在极少情况下,你可以将它设置成自定义的组。建议使用预定义的permission组,这样可以简化权限用户界面显示。
要注意的是Label(标题)和Description(描述)这两个属性也是必须的。它们的值为字符串或字符串资源,用来显示给用户看。标题应当简洁明了,简要标识该权限需要保护的功能,而描述可以描述一下具有该权限时能够执行的操作。Android对于描述使用的惯例是两行,第一行描述该权限,第二行给出警告,在拥有该权限时有那些不好的事情可能发生。
下例为使用CALL_PHONE权限时的Label和Description的示例:
- <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>
在AndroidManifest.xml中强制执行权限
在程序清单文件AndroidManifest.xml中可以定义高一层次的权限申明以限制应用中整个组件的访问。这需要在指定的组件描述中添加Android:permission属性来定义需要哪些权限才能够使用该组件。
· Activity权限 (应用到<activity>标记)限制谁可以启动该Activity。这个权限的检查是在调用Context.startActivity()和Activity.startActivityForResult()时进行。如果调用者不拥有所需权限系统将抛出SecurityException异常。
· Service 权限 (应用到<service>标记)限制谁可以启动或绑定该Service。该权限的检查是在调用Context.startService(),Context.stopService()和Context.bindService()时进行。如果调用者不拥有所需权限系统将抛出SecurityException异常。
· BroadcastReceive权限(应用到<receive>标记)限制谁可以发送广播信息给指定的Receiver。该权限的检查发生在Context.sendBroadcast()返回以后,系统会试图将提交的广播信息发送到指定的Receiver。因此,此时权限检查失败不会给调用者抛出异常。系统只是不发送该信息。同样,可以为Context.registerReceiver()添加权限控制以控制谁可以发送广播消息给使用程序注册的Receiver。此外,也可以为Context.sendBroadcast()添加权限控制以限制哪个BroadcastReceiver可以接受广播消息。
· ContentProvider权限(应用到<provider>标记)限制谁可以访问指定ContentProvider中的数据(ContentProvider还支持额外的非常重要的安全机制—URI权限,参见后面内容)。和其它程序组件不同的是,ContentProvider权限可以定义两个权限,一是android:readPermission限制谁可以读取指定ContentProvider中的数据,另外一个是android:writePermission限制谁可以写数据到指定ContentProvider。要注意的是,如果某个ContentProvider同时定义了读写权限,如果只拥有写权限并不意味着你可以读取该ContentProvider中的数据。该权限的检查是在第一次获取该ContentProvider对象时进行,如果调用者不拥有两个权限中任意一个,系统将抛出SecurityException异常。在使用ContentProvider进行读写操作时,ContentResolver.insert(),ContentResolver.query()需要调用者拥有读权限,而ContentReslover.delete()和ContentResolver.update()需要调用者拥有写权限。如果调用者不具有所需权限系统将抛出SecurityException异常。
在发送广播消息时执行权限
除了上面说的用来限制谁可以给注册过的BroadcastReceiver发送消息的权限外,你也可以在调用Context.sendBroadcast()方法发送广播消息时添加权限,用来限制可以接收该种广播消息的BroadcastReceiver必须拥有的权限。
要注意是,广播消息的发送者和接收者都可以使用权限。此时,两类权限都需要通过检查才能完成消息的顺利传送。
其它权限强化机制
在使用某个Service时,可以执行进一步细化的权限。这是通过调用方法Context.checkCallingPermission() 方法来完成的。通过传入所需的权限定义字符串,该方法将返回一个整数来标明调用进程是否拥有指定权限。要注意的该方法只能在调用其它进程中某个方法时使用,通常是通过由Service定义的IDL接口来定义的。
此外还几种有用的方法来检查权限,比如你已知其它进程的标识符(ID),你可以使用Context.checkPermission(String,int,int)方法来检查指定进程中某个权限的状态。或者你有某个其它应用安装包的名称,你也可以直接使用PackageManager对象的Context.checkPermission(String,Sting)方法来检查指定的安装包是否已被赋予了所检查的权限。
URI权限
上面介绍过的权限机制对于使用ContentProvider时常常不能满足要求。一个ContentProvider可以使用读,写权限来保护其中的数据,而使用ContentProvider的客户端也需要把指定的URI资源发给其它某个应用并由其来处理。一个典型的例子为邮件中的附件。访问邮件应该使用权限来控制,因为邮件是有关用户隐私的数据。然而,如果一个指向图像的URI资源发送个某个图像浏览应用,这个图像浏览应用不用户访问邮件的权限(它也没有理由需要有访问邮件的权限)。
解决这种问题的方法是使用URI权限。在启动一个Activity或者返回结果给某个Activity时,调用者可以设置Intent.FLAG_GRANT_READ_URI_PERMISSION或 Intent_FLAG_GRANT_WRITE_URI_PERMISSION标记。这将赋予接收URI资源的Activity相应的权限来访问Intent中特定的URI数据,而无需关心接收URI资源的Activity是否拥有对给定ContentPrvoider的访问权限。
然而赋予这种URI权限依然需要包含这些URI资源的ContentProvider配合。强烈建议指定的ContentProvider在<grant-uri-permission>标记中通过属性android:grantUriPermissions定义它支持的URL权限。