04 System Permissions–系统权限

原文链接:http://developer.android.com/guide/topics/security/permissions.html

Android系统是一个特权分离的操作系统,在这个系统中每一个应用运行时都有一个不同的系统标识(Linux user ID和group ID)。系统的一部分也被分割为不同的标识。这样Linux将应用从其他应用和系统分离开。

另外更细的安全特性由“permission”机制来提供,它强制限制某个进程可以执行的特定操作,和授予per-URI权限来临时访问特定的数据。

这篇文章描述了应用开发者如何使用Android提供的安全特性。Android Open Source Project提供了一个更具体的Android安全概述

Security Architecture
安全架构


Android安全架构的中心设计思想是应用程序没有权限去做任何可能严重影响其他应用程序、系统或用户的操作。这些操作包括读写用户私人数据(例如联系人和电子邮件)、读写其他应用程序的文件、执行网络访问操作、使设备保持唤醒等等。

因为每一个Android应用操作在一个进程沙盒中,应用必须明确的共享资源和数据。它们做这个通过定义它们需要基础沙盒不提供的额外的功能的权限。应用程序静态的定义它们需要的权限,并且Android系统在安装应用的时候提示用户同意。Android系统没有动态授予权限的机制(在运行时)因为它会导致用户对安全损害的体验复杂化。

应用程序沙盒并不依赖用于编译应用程序的技术。特别的Dalvik VM并不是一个安全的边界,并且任何都可以运行本地代码(参考Android NDK)。所有类型的应用程序——Java、本地代码、混合的——都以同样的沙箱方式并且有相同的安全度。

Application Signing
应用程序签名


所有的APK文件必须由它们的开发者使用他们自己的私有证书签名。这个证书定义了应用程序的作者。这个证书不需要由证书机构认证;Android应用程序使用它自己签名的证书是完全允许的。证书在Android应用程序中的目的是用来区分应用的作者。则允许系统授权或拒绝应用访问signature-level permissions和授权或拒绝一个应用程序请求被授予与另一个应用程序相同的Linux进程ID(request to be given the same Linux identity)。

User IDs and File Access
用户ID和文件访问


在安装的时候,Android系统为每一个包分配一个不通的Linux用户ID。这个ID在这个应用程序在设备上的生命周期中保持不变。在不同的设备上,同样的包可能用不同的UID;需要注意的是每一个包在设备上都有一个不同的UID。

因为安全机制在进程级别发生,任意两个包的代码一般不可能在同一进程上运行,除非它们需要以不通的Linux用户来运行。你可以在AndroidManifest.xml文件中每一个包下的manifest标签下使用sharedUserId属性给它们分配同一个user ID。如果这样做了,出于安全的考虑这两个包之后会被视为相同的应用程序,有相同的user ID和文件权限。注意为了保持安全,只有两个应用程序有相同的证书(并且请求相同的sharedUserId)签名的时候才会被授予相同的用户ID。

任何被应用程序存储的数据都会被应用程序的user ID签名,并且对其他包不可用。当使用getSharedPreferences(String, int))、openFileOutput(String, int))或者openOrCreateDatabase(String, int, SQLiteDatabase.CursorFactory))创建文件的时候,你可以使用MODE_WORLD_READABLE或者MODE_WORLD_WRITEABLE标志来允许其他包读写文件。当设置了这些标志,文件任是由你的应用程序所有,但是它对任何的应用程序来说都是可以读写的。

Using Permissions
使用权限


一个基本的Android应用程序默认没有与之关联的权限,这意味着它不能做任何对用户体验和设备上的数据造成负面影响的事情。为了使用设备上受保护的特性,你必须在你的AndroidManifest.xml文件中包含一个或者多个<user-permission>标签来定义你的应用需要的权限。

例如,一个应用需要监视收到的短信息需要指定:

在应用安装的时候,包安装器会请求是否准许应用程序所需的权限,基于声明这些权限的应用程序的签名和/或与用户的交互。应用程序在运行的时候不会检查权限;应用程序在安装的时候被准许一个特定的权限并且当需要的时候可以使用这些特性,或者权限不被准许和企图使用特性失败的时候会提示用户。

通常情况下,一个权限请求失败会返回一个SecurityException异常给应用程序。然而,并不能保证任何时候都会这样。例如,sendBroadcast(Intent))方法检查权限作为数据是否已经被投递给每一个接收者,在方法调用返回之后,所以你可能不会接收到一个异常当权限失败的时候。然而,在几乎所有情况下一个权限失败将在系统log中被打印出来。

然而,在一个正常用户的情况下(例如当应用程序从Google Play商店安装的时候),一个应用可能不会被安装如果用户不准许应用需要的每一个权限。所以你大体上不需要担心在运行时由丢失权限导致的失败,因为事实是在应用程序安装的时候你的应用已经被授予了它需要的权限。

由Android系统提供的权限可以在Manifest.permission中找到。任何应用可以定义和强制它自己的权限,所以这并不是一个所有可能权限的列表。

一个特定的权限可能在你应用操作的多个地方被强制:

  • 在调用系统的时候,为了防止一个应用程序执行某些功能。
  • 当启动一个activity的时候,为了防止应用程序启动其他应用的activity。
  • 发送和接收广播的时候,为了控制谁能接收你的广播或者谁可以给你发送广播。
  • 当访问和操作一个content provider的时候。
  • 绑定或者启动一个servier的时候。

警告:随着时间的推移,新的规定可能被加入平台,例如,为了使用某些API,你的应用必须请求一个之前可能没有的权限。因为现有的应用程序可以自由的访问这些API,Android系统可能在应用程序的manifest文件中应用新的权限的请求以避免在新的平台版本上应用无法运行。Android系统做这些决定依据应用是否需要这些权限基于targetSdkVersion属性提供的值。如果值小于权限假如的版本则Android系统加入这些权限。

例如,WRITE_EXTERNAL_STORAGE权限在API level 4时加入允许访问共享的存储空间。如果你的targetSdkVersion小于3或者更低,这个权限会被在新的Android版本加入你的应用。

当心它是否发生在你的应用上,你在Google Play商店上的应用需要展示请求的权限计算你的应用可能并不真的需要它们。

为了避免这样移除你不需要的权限,保持你的应用的targetSdkVersion尽可能的高。你可以在Build.VERSION_CODES文档中查看每一个权限在哪个发布版本中被添加。

Declaring and Enforcing Permissions
声明和执行权限


为了执行你自己的权限,你必须先在你的AndroidManifest.xml文件中定义它们使用一个或多个<permission>标签。

例如,一个应用程序想要控制谁可以启动它的activity可以定义为这个操作定义如下的权限:

<protectionLevel>属性是必须的,告诉系统如何告知用户应用程序需要的权限,或者谁被允许掌握这些权限,在链接的文档中有详细描述。

<permissionGroup>属性是可选的,仅仅是用来帮助系统向用户展示权限。你大体上可能设置它为一个标准的系统组(在android.Manifest.permission_group中)或者在罕见的情况下由你自己定义的权限。优先使用现有的组,这样会简化展示给用户的权限的UI。

注意必须为一个权限提供标签和描述。这些字符串资源会在用户查看权限列表(android:label)或一个权限的详细信息(android:description)的时候显示。标签应当短,关键是用几个单词就能描述权限的功能。描述应当是几个句子来说明权限允许使用者做什么。我们的约定是描述使用两个句子,第一句描述权限,第二句警告用户当应用程序请求权限的时候可能会发生什么坏的情况。

这里有一个CALL_PHONE的权限的标签和描述:

你可以通过设置应用和shell命令adb shell pm list permission来查找当前定义在设备上的权限。如何使用设置应用,Settings->Applications。选择一个应用并且滚动来查看应用使用的权限。对于开发人员,adb ‘-s’选项在一个表格中显示权限就像用户看到它们的效果一样:

Enforcing Permissions in AndroidManifest.xml
在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.sendBroadcase())方法返回之后权限会被检查,随着系统试图将广播投递给给定的接收者。作为结果,权限失败在抛出的给调用者的异常中将没有结果,它仅仅只会不投递intent。同样的方式,一个权限可以被提供给Context.registerReceiver())来控制谁可以广播给一个代码注册的接收者。另一种方式,一个权限可以被提供当调用Context.sendBroadcase()来限制哪一个广播接收者对象被允许接收广播(详见下面)。

ContentProvider权限(使用<provider>标签)限制谁可以访问ContentProvider中的数据。(Content Provider有一个重要的额外可用的安全特性通过调用URI permissions,会在下面描述。)不像其他的组件,有两个分开的属性你可以设置android:readPermission限制谁可以从provider中读取,android:writePermission限制谁可以向provider中写入。需要注意,如果一个provider既被读权限又被写权限保护着,仅仅拥有写权限并不意味着你可以从provider中读。权限会在你第一次检索provider的时候(如果你没有任何一个权限则会抛出SecurityException异常)被检查;并且作为你在这个provider上的操作。使用ContentResolver.query())来请求获取读权限;使用ContentResolver.insert()),ContentResolver.update()),ContentResolver.delete())来请求写权限。在所有的情况中,如果没有获取请求的权限则会在结果中抛出SecurityException异常。

Enforcing Permissions when Sending Broadcasts
在发送广播时使用权限

除了允许谁能给注册的BroadcastReceiver发送intent(见上面的描述),你也可以在发送广播的时候指定一个所需的权限。通过调用Context.sendBroadcase())并传递一个权限字符串,你需要接收者的应用必须拥有这个权限为了接收你的广播。

需要注意,一个接收者和一个广播者都可以请求权限。在这种情况下,两个权限都必须被检查通过为了Intent可以传递到有关联的目标。

Other Permission Enforcement
其他强制权限

任意细粒度的权限可以在调用任一服务的时候被执行。这通过Context.checkCallingpermission())方法来完成。通过调用一个所需权限的字符串,它会返回一个整数指示当前调用的进程使用该权限。需要注意,这只能在你从其他进程执行调用的时候被使用,通常通过服务公开的IDL接口或者其他方式给另一个进程。

还有许多其他有用的方式来检查权限。如果你有其他进程的pid,你可以使用Context方法中的Context.checkPermission(String, int, int))来检查pid对应的权限。如果你有另一个应用程序的包名,你可以直接使用包管理器的方法PackageManager.checkPermission(String, String))来查找指定的包是否已经被授予了一个特定的权限。

URI Permissions
URI权限


到目前为止说描述的标准权限系统当使用content providers时往往是不够的。一个Content Provider可能通过读和写权限来保护它自己,所以它的直接客户端通常需要手持特定的URI来为其他应用提供操作。一个典型的例子是电子邮件应用中的附件。对邮件的访问应当被权限保护,因为它是敏感的用户数据。然而,如果一个图片附件的URI被传递给一个图片查看器,这个图片查看器不会有权限来打开附件直到它无条件的拥有访问所有电子邮件的权限。

解决这个问题的方案是per-URI权限:当打开一个activity或者返回一个结果给activity,调用者可以设置Intent.FLAG_GRANT_READ_URI_PERMISSION和/或Intent.FLAG_GRANT_WRITE_URI_PERMISSION。这允许接收activity有权限访问Intent中特定数据的URI,不用理会它是否有任何访问content provider中与Intent对应的数据的权限。

这种机制允许一个通用的capability-style模型,用户交互(操作一个附件,从列表中选择一个联系人,等等)驱动特别授予的细粒度的权限。这是一个关键,减少应用程序所需的权限只关注那些与它们的行为直接相关的权限。

细粒度的URI授予权限需要与掌握这些URI的content provider合作。强烈建议content provider实现这个功能,并且定义它们通过android:grantUriPermissions属性或者<grant-uri-permissions>标签来支持这个功能。

更多的信息可以在Content.grantUriPermission()),Context.revokeUriPermission())和Context.checkUriPermission())方法中找到。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">