Android API Guides –System Permissions

系统权限

 

声明:

本文由Gordon翻译

发布于www.dlvoice.com

欢迎转载,但请保留此声明

原文地址:http://developer.android.com/guide/topics/security/permissions.html

         Android是一个特权分离的操作系统,运行在其上的应用都有一个特定的系统身份(Linux的用户ID和组ID)。系统的部分也会被分为特定的身份,Linux就是通过这个身份来区别各个应用的。

更加详细的安全特性是通过“权限”机制来控制一个进程的特定操作是否可以执行。它通过每一个URI的权限来来决定他们是否可以访问特定的数据。

本文将会介绍开发者如何使用Android提供的安全特性。更基础的文章“Android Security Overview”可以在Android的开源项目中查看。

安全架构

Android安全架构设计的核心理念就是没有一个应用可以破坏另外一个应用,操作系统或者用户。这包括读写用户的私有数据(比如联系人和email),读写另外一个应用的文件,进行网络访问,保持设备一直醒着或者别的操作。

因为每一个应用都是工作在进程封装上,所以它必须明确地分享资源和数据。他们可以通过声明他们需要的权限来实现资源和数据的共享。应用静态声明他们的权限,然后系统在安装应用的时候请求用户同意应用获得这些权限。

应用的封装并不是由编译应用的技术来决定的,Dalvik虚拟机(VM)并不是一个特殊安全的界限,每一个应用都可以运行本地的代码(参考Android NDK)。每一种类型的应用——Java,本地以及混合的——他们都是用同样的方式来进行封装的,并且他们的安全等级也是一样的。

应用的签名

所有的APK文件都必须进行签名,而且签名使用的是包含开发者私有密钥的证书。这个证书指明了应用的作者。这个证书并不需要一个证书认证来进行签名。通常来说,Android的应用使用一个自签名的证书就足够了。证书的目的就是为了区分应用的作者。这就使得系统可以判断应用是否可以访问签名级别的权限,以及是否允许别的应用和这个应用使用同样的Linux身份(ID)。

用户ID和文件存取

在安装的时候,Android给每个包一个固定的Linux用户ID,同一设备上这个ID将会伴随这个包一生。当然不同设备上,同一个应用包可能会有不同的用户ID。不管怎样在特定的设备上每个包都有一个特定的UID。

因为安全相关的操作都是在进程级进行执行的,任何两个应用包的代码不能在同一进程运行,因为他们需要在不同的Linux用户上进行运行。若你想两个应用使用同样的用户ID运行,只要设置AndroidManifest.xml文件的manifest标签中的sharedUserId属性相同即可。这样做了之后,在安全层面来看,这两个应用将会被认为是同一个应用,具有相同的用户ID和文件权限。注意,只有两个使用同样签名的应用(当然SharedUserID也得相同)才会给予同样的用户ID。

应用存储的任何数据都应当指定为这个应用的用户ID,并且不能被别的应用包访问。当使用getSharedPreferences(String, int), openFileOutput(String, int)或者openOrCreateDatabase(String, int, SQLiteDatabase.CursorFactory)创建文件时,你可以使用MODE_WORLD_READABLE或者MODE_WORLD_WRITEABLE标志来允许别的应用对这个文件的读写。当这些标志被设置之后,这个文件仍然是属于你的应用,但是他的全局读写权限将会被设置,从而让别的应用可以使用它。

权限的使用

一个基本的Android应用默认来说是没有相关权限的,这也就意味着它不能做任何破坏用户体验及设备数据的操作。为了保护设备的特性,你必须在AndroidManifest.xml文件中声明一个或者多个<uses-permission>标签。

例如,一个需要监听SMS信息的应用应当设定如下:

1
2
3
4
5
<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发送给应用,但是并不是在任何地方都会产生这个exception。举例来说,sendBroadcast(Intent)方法因为要把数据发送给每一个receiver,在这个方法返回的时候它会检查他们的权限,但是若是有权限的失败,你不会收到exception。当然,几乎所有的权限失败的情况,都会被打印到系统log中。

然而,正常用户使用的情况下(比如应用从Google应用商店里安装),若是用户不允许应用申请的权限,这个应用就不会被安装成功。所以,一般来说你没有必要担心运行时缺少权限,因为事实上你的应用在安装的时候就已经获得了它想要的相应权限。

Android系统提供的权限可以参考Manifest.permission文件。任何应用也都有可能定义并支持它自己的权限,所以这个列表也不能包含所有的可能权限。

  • 一个特定的权限可能在你的应用操作的一系列地方被实施:
  • 当调用到系统的时候,为了防止执行特定的函数。
  • 当开始一个activity的时候,为了防止一个应用调用另外一个应用的activity。
  • 在发送和接收broadcast,在控制谁能接收你的broadcast或者谁能发送broadcast给你。
  • 绑定或者启动service。

注意:

随着时间的推移,平台可能会增加新的权限要求,所以为了使用特定的API,你的应用需要请求一些之前不用请求的权限。因为已经存在的应用可能认为访问这些API是直接可用的,Android可以在应用的manifest文件中直接申请新的权限请求从而避免在新的平台版本上破坏原有的应用。Android作出应用可能需要权限的描述是基于targetSdkVersion属性的。假如这个的值小于权限假如的版本,那么Android就加入对应的权限。

举例来说,WRITE_EXTERNAL_STORAGE权限是在API级别4中加入的,它是为了防止访问共享的存储空间。假如你的targetSdkVersion小于3,那么在新的Android版本中会把这个的权限加入到你的应用中。

需要注意的是,假如这种情况在你的应用中发生,即使这些权限在你的应用中可能没有真正地请求,Google应用商店也会在显示你的应用的时候请求这些权限。

为了避免这种情况发生,你需要及时把你的targetSdkVersion更新到足够高的版本。你可以参考Build.VERSION_CODES文档来看每次发布都加入了哪些权限。

声明和实施许可

为了实施你的权限,你需要首先在AndroidManifest.xml文件中使用<permission>标签来声明他们。

例如,一个应用想要控制谁可以启动它的activity,就应当为这个操作声明一个权限,具体的方法如下:

1
2
3
4
5
6
7
8
9
<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>属性是需要的,它是用来告诉系统应用需要这个权限的时候是如何来通知用户的,或者谁是被允许得到这个权限的,具体的描述可以参考链接的文件。(译者注:后期翻译ok会加入)。

<permissionGroup>属性是可选的,只是用来让系统向用户显示权限的。通常你可以把它设为一个标准的系统组(在android.Manifest.permission_group中有列出)或者有时候你也可以设为你自己定义的内容。推荐是使用一个已经存在的组,这样会简化向用户显示的权限UI。

注意应当为权限提供标签和描述。当用户看到一个权限的列表(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>

你可以通过设置应用和shell命令“adb shell pm list permissions”来查看当前系统定义的权限。若是使用设置应用,可以在设置->应用下面进行查看。选取一个用户,向下滚动,可以看到应用使用的权限。对开发者来说,adb –s的选项将会以用户看到的形式来显示对应的权限:

1
2
3
4
5
6
7
8
9
10
11
12
$ 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>标签)限制了谁可以启动和绑定对应的service。这个权限是通过Context.startService(),Context.stopService()以及Context.bindService()来检查的。假如调用者没有这个权限,同样会产生一个SecurityException。

BroadcastReceiver权限(<receiver>标签)限制了谁能向相关的receiver发送广播。这个权限是在Context.sendBroadcast()之后检查权限的,因为这个时候系统会把广播发送给特定的接受者。这样一来,权限的失败将不会产生一个exception返回给调用者,他只会不发送intent而已。同样的,权限可以通过Context.registerReceiver()来控制谁能够广播给一个通过程序注册的receiver。另一方面,当调用context.sendBoradcast()的时候也会提供权限来限制哪一个BoradcastReceiver对象可以接收这个广播。(详情见下面)

ContentProvider权限(<provider>标签)限制了谁可以访问ContentProvider的数据。(Content provider还有一个重要的额外可用的安全机制称之为URI权限,这个会在稍后进行介绍)。和别的组件不同,这里有两个独立的权限属性可以设置:android:readPermission限制了谁可以读这个provider,android:writePermission限制了谁可以写它。注意,provider是由读写权限分别保护的,获得些权限不意味着你可以读。这个权限是在你第一次检索provider进行检查的(假如你没有权限,一个securityException将会抛出),当然你在这个provider上进行操作的时候也会检查相应的权限。可以使用ContentResolver.query()函数来请求得到读的权限,使用ContentResolver.insert(),ContentResolver.update(), ContentResolver.delete()来请求写的权限。在所有这些情况中,若是没有得到相应权限都会抛出一个SecurityException。

当发送广播的时候执行权限

除了上文提到的对于注册过的BroadcastReceiver发送intent的时候执行权限之外,你还可以在发送广播的时候请求权限。在调用Context.sendBroadcast()的时候,加入一个权限字符串,你就可以要求这个接收的应用必须有响应的权限才能接收广播。

注意,发送者和接收者都可以请求权限。这种情况下,在发送intent和接收端都需要进行相应的权限检查。

其他的权限执行

更细的权限会在调用service时执行。这个是通过Contex.checkCallingPermission()方法来实现的。在调用这个方法的时候传入一个权限字符串,就会返回一个整形值用来表示当前的进程是否已经获得了对应的权限。注意,这个只会在别的进程执行一个调用才会被使用,通常来说是通过一个service的IDL接口或者提供给别的进程的其他方法来实现。

还有一些别的方式来检查权限。假如你有另外进程的pid,你可以使用Context.checkPermission(String, int, int)来检查对应的权限。假如你有别的应用的包名字,你可以使用PackageManager.checkPermission(String, String)来检查对应的包是否获得对应的权限。

URI权限

到目前为止所描述的标准的权限系统对content providers的使用来说都不是很好。content provider可能想要保护它自己的读写权限,尤其是当它的直接使用者对别的应用操作获取特定的URI的时候。一个典型的例子就是邮件应用中的附件。邮件的访问需要权限进行设置,因为毕竟用户的数据是敏感的。然而,假如图片浏览器获得了一个图片附件的URI,这个图片浏览器将不会有打开图片的权限,因为它没有道理去获得访问邮件的权限。

这个问题的解决方法就是每一个URI的权限:当启动一个activity或者返回一个结果给activity的时候,调用者可以设置Intent.FLAG_GRANT_READ_URI_PERMISSION或者Intent.FLAG_GRANT_WRITE_URI_PERMISSION。这个允许接收activity获得访问intent中特殊数据URI的权限,而不用管他是否有权限访问这个Intent提供者的数据权限。

这个机制允许了一个用户交互时(打开一个附件,选择一个联系人等)对专用的更细的权限访问的通用模型。这将会是减少应用需要权限的一个关键所在,应用只需要那些他们特性直接相关的权限即可。

然而对细粒度URI权限的获取,需要这些URI的content provider也要做一些相应的操作。我们推荐这些content provider实现这个功能,他们只需要通过android:grantUriPermissions属性或者<grant-uri-permissions>标签来声明即可。

更多的资讯请参考Context.grantUriPermission(),Context.revokeUriPermission()和Context.checkUriPermission()方法。

 

推荐您继续阅读以下内容:

Permission that Imply Feature Requirements

关于如何通过请求权限来限制你的应用只在包含对应硬件或者软件特性的设备上运行。

<uses-permission>

Manifest标签下的API参考,他声明了应用需要的系统权限。

Manifest.permission

所有系统权限的API参考。

以下内容你可能也感兴趣:

Device Compatibility

关于Android在不同类型设备上运行的资讯,他会介绍如何针对不同设备进行优化你的应用以以及如何在不同设备上运行你的应用。

Android Security Overview

更详细的关于Android平台的安全模式。

猜你喜欢

转载自blog.csdn.net/u011960402/article/details/47774931