Devel/security
From Android中文网
目录 |
[编辑] Android中的安全与许可
Android是一个多进程系统,每个应用(以及系统的部分)运行在自己的进程中。很多应用和系统间的安全通过标准Linux工具在进程级执行,比如为应用分配的用户和组标识。额外的细粒度安全特性通过“许可”机制来提供,该机制能够对一个指定进程可实现的特定操作进行约束。
[编辑] 用户标识和文件访问
每个安装在设备上的Android包(.apk)文件,Linux都赋予它一个唯一的用户标识,创建一个沙盒(sandbox)并防止它触及其它应用(同样,也避免其它应用触及它)。这个用户标识是在应用安装在设备上时赋予的,并且通常该应用在设备上的整个生命周期内该标识保持不变。
因为安全enforcement发生在进程级,而正常情况下,每个进程都需要作为不同的Linux用户来运行,任意两个包的代码都不会运行在同一进程中。你可以使用每个包内AndroidManifest.xml中的Manifest标签下的sharedUserId属性来给它们赋予相同的用户标识。通过这样做,为了安全起见,这两个包就会别当作相同的应用,具有相同的用户标识和文件许可。注意:为了保持安全,只有两个具有同样签名(并且请求相同的sharedUserId)的应用才会赋予相同的用户标识.
应用创建的任何文件都会被赋予应用的用户标识,并且,正常情况下不能被其它包访问。当使用getSharedPreferences(String, int), openFileOutput(String, int)或者createDatabase(String, int, int, SQLiteDatabase.CursorFactory)来创建应用时,你可以使用MODE_WORLD_READABLE和/或MODE_WORLD_WRITABLE标志位来允许其它包读/写该文件。当设置了这些标志位,文件虽然始终为你的应用所有,但是它的全局可读/写许可已经被适当设置,所有的应用都可以看到它。
[编辑] 使用许可
一个基本Android应用没有与之关联的许可,这意味着它不能做任何会破坏该设备上用户体验或数据的事情。为了利用设备的保护特性,你必须在你的AndroidManifest.xml中包括一个或多个<uses-permission>标签来声明你的应用所需要的许可。
例如,需要监控流入SMS消息的应用会说明以下条件:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.google.android.app.myapp" > <uses-permission id="android.permission.RECEIVE_SMS" /> </manifest>
在应用安装时,应用请求的许可由包安装器基于可信权威检查和与用户的交互情况来授予。在应用运行期间对用户不做检查:它要么在安装时被授予特定的许可,并且使用想用的特性;要么不被授予许可,并且使得一切使用特性的尝试失败而不提示用户。
通常,多次的许可错误会导致产生扔回应用的SecurityException。然而,这并不保证在任何地方都会发生。例如,broadcastIntent(Intent)方法就是当数据被发送给到每个接收器时检查许可的,在方法调用返回之后,因此当许可失败时你不会收到一个异常。然而,几乎在所有例子中,许可失败都会被打印到系统日志中。
Android系统提供的许可可以在Manifest.permission中找到。每个引用也可以定义和enforce它自己的许可,因此这不是全面的所有可能的列表。
一个特定许可可以在你操作程序过程中的很多地方被执行:
- 在做系统调用时,防止应用执行特定功能。
- 在启动Activity时,防止一个应用启动其它应用的activities。
- 发送和接收Intent广播时,控制谁能接收你的广播或者谁能发送广播给你。
- 当在内容服务方进行访问或操作。
- 绑定或启动一个服务。
[编辑] 声明和enforce许可
为了enforce你自己的许可,你必须首先在你的AndroidManifest.xml中使用一个或多个[[<permission>]]标签声明它们。
例如,一个应用程序想用控制谁能启动一个activities,它可以为声明一个做这个操作的许可,如下:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.google.android.app.myapp" > <permission id="com.google.android.app.myapp.permission.DEADLY_ACTIVITY" android:label="@string/permlab_deadlyActivity" android:description="@string/permdesc_deadlyActivity" /> </manifest>
注意:应该为每个许可提供标签(label)和描述(description)。当用户浏览许可列表时,它们是可以展示给用户的字符资源,如(android:label)或者一个许可的详细信息(android:description)。标签(label)应该比较短,用几个字来描述该许可保护的关键功能点。描述(description)应该是一组句子,用于描述获得许可的用户可以做什么。我们写描述的习惯是两句话,第一句声明许可,第二句警告用户如果应用认可该许可时,会发生什么不好的事情。
下面是一个CALL_PHONE许可的例子:
<string name="permlab_callPhone">Call Phone Numbers</string>
<string name="permdesc_callPhone">Allows application to call
phone numbers without your intervention. Bad applications may
cause unexpected calls on your phone bill.</string>
[编辑] 在AndroidManifest.xml中enforce许可
用来约束到整个系统/应用组件的访问的高级许可,可以通过你的AndroidManifest.xml来设置。实现这种设置,需要在预期组件上有一个android:permission属性,并给该访问控制许可命名。
Activity许可(应用于[[<activity>]]标签)约束谁能够启动相关活动。该许可在Context.startActivity()和Activity.startSubActivity()期间被检查;如果呼叫者没有必须的许可,那么该呼叫将会抛出SecurityException.
Service许可(应用于[[<service>]]标签)约束谁能够启动或绑定到相关的服务。该许可在Context.startService(),Context.stopService()以及Context.bindService期间被检查;如果呼叫者没有必须的许可,那么该呼叫将会抛出SecurityException。
IntentReceiver许可(应用于[[<receiver>]]标签)约束谁能向相关接收者广播Intent对象。该许可在Context.broadcastIntent()返回之后,在系统把提交的Intent发送给指定的接受者时被检查。所以,这种许可失败不会向呼叫者抛出异常;而只是不会发送intent。同样,在Context.registerReceiver()中也可以提供许可来控制谁能广播到一个已经程序注册的接收者。另一方面,当调用Context.broadcastIntent()时,许可可以用来约束哪些IntentReceiver对象被允许接受广播(见下方)。
ContentProvider许可(应用于[[<provider>]]标签)约束谁能访问ContentProvider中的数据。于其他组件不同,你可以设置两个许可属性:android:readPermission用来约束谁能从提供者那里读,android:writePermission用来约束谁能向提供者写。注意:如果提供者被读写许可所保护,拥有写权限并不意味着你可以从提供者那里读到数据。该许可在你首次访问提供者(如果你不具有任一许可,将会抛出SecurityException),并且在提供者上做操作时被检查。使用ContentResolver.query()需要有读许可;使用ContentResolver.insert(),ContentResolver.update(), ContentResolver.delete()或Cursor.commitUpdates()需要写许可。在所有这些情况下,没有必须的许可会导致呼叫抛出SecurityException。
[编辑] 在广播Intents时enforce许可
许可除了可以enforcing谁能向注册的IntentReceiver发送Intents(如上说述)之外,你也可以在广播Intent时指定一个许可。要想带一个许可字符串参数调用Context.broadcastIntent(),要求接收者的应用必须具有该许可才能接受你的Intent。
需要注意是接收者和广播者都能要求许可。当这种情况发生时,Intent发送到相关目标必须要通过两个许可检查。
[编辑] 其它许可执行
在调用服务时,可以任意细粒度的许可检查。这是用Context.checkCallingPermission()方法来实现的。用一个理想的许可字符串调用,它将返回一个整型数来指示许可是否被目前调用过程认可。需要注意的是,这种方法只能在你执行其他进程的调用时使用,通常通过一个服务发布的IDL接口或一些进程指定的方法。
还有一些其他的好方法来检查许可。如果你有进程的pid,你可以使用Context方法Context.checkPermission(String, int, int)来向那个pid做许可检查。如果你有另一个应用的包名称,你可以使用直接的PackageManager方法Context.checkPermission(String, String)来检查指定包是否认可一个特定许可。
