Android官方API Guide学习之三 系统权限

  Android是一个权限分隔的系统,每一个应用程序都在一个特定的系统标识符(Linux的User ID和Group ID)下运行。系统的各个部分也具有特定的身份标识。从而让Linux将应用程序之间,应用程序和系统间相互隔离。

安全架构
  Android安全架构的中心设计理念是:在默认情况下,没有任何一个程序具有执行对其他应用程序、系统或者用户不利操作的权限。如读写用户的私密数据(例如联系人、邮件等),读写其他程序的文件、执行网络访问,保持设备一直处于唤醒状态等等。
  因为所有的应用程序在一个进程沙箱上进行操作,因此我们需要明确地表明每个应用程序的它所共享的资源和数据。除了基本的进程沙箱中的操作外,应用程序通过声明它所需要的“许可”来获取额外的功能权限。应用程序静态地声明它所需要的功能“许可”,而Android操作系统则负责告诉用户,征求其同意。
  
应用程序签名
  所有的APK(.apk文件)必须用开发者私有证书进行签名。此证书标识应用程序的作者。该证书并不需要由证书颁发机构签署,典型地,Android应用程序可使用自签名证书。Android中证书的目的是区分应用程序的作者。这使系统能够允许或拒绝应用程序访问签名级别的权限,并授予或拒绝应用程序请求赋予相同的Linux身份来作为另一个应用程序。

用户ID和文件访问
  在安装的时候,Android给每个安装包一个独特的Linux用户ID。该身份在应用程序安装后到卸载前是恒定不变的。在不同的装置中,相同的安装包具有不同的UID;重要的是,每个包在特定设备上的都有一个不同的UID
  因为安全实施发生在进程级别上,任意两个包的代码需要作为不同的Linux用户运行,所以他们不能正常的在同一个进程中运行。你可以在每个包的AndroidManifest.xml的“manifest”标签中使用“sharedUserId”属性来给他们分配相同的用户ID。通过这样做,出于安全目的这两个包将被当做是拥有相同的用户ID和文件许可的同一个应用。既然为了保留安全性,只有两个应用被签有相同的签名(并且请求相同的sharedUserId)才能被赋予相同的用户ID。
  一个应用程序存储的数据被赋予该应用程序的UID用户所有,其他程序不能正常访问。当通过调用getSharedPreferences(String, int), openFileOutput(String, int), 或者openOrCreateDatabase(String, int, SQLiteDatabase.CursorFactory)方法 创建一个新文件时,可以用MODE_WORLD_READABLE and/or MODE_WORLD_WRITEABLE两个标签来运行其他程序进行文件的读写。设置这些标签后,文件仍属于该用户,但是它被赋予的全局访问权限,所以其他程序也能够进行读写操作。

使用“许可”
  在安全架构里提到,在默认情况下,没有任何一个程序具有执行对其他应用程序、系统或者用户不利操作的权限。为了让应用程序能够执行受保护的功能,我们必须在manifes文件中使用<users-permissions>标签来声明。
  如,应用程序需要监控手机短信,那么需要在manifest文件中声明:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.android.app.myapp" >
    <uses-permission android:name="android.permission.RECEIVE_SMS" />
    ...
</manifest>

  如果应用程序声明的<users-permissions>都是普通权限(即对用户的隐私或设备不构成太大风险的操作权限),系统自动授予这些权限。如果你的应用程序声明中包含危险权限,(即可能会影响到用户的隐私或设备的正常运行的操作权限),系统会要求用户明确授予的权限。Android权限请求方式取决于系统版本和应用程序的target system version。

  • 如果设备运行的是Android6.0(API级别23)或更高,以及应用程序的targetSdkVersion为23或更高,则应用程序在运行时请求权限。用户可以随时撤销的权限,因此应用程序每次运行时需要检查是否具有该权限。
  • 如果设备运行的是Android5.1(API级别22)以下,或应用程序的targetSdkVersion是22或更低时,当用户安装应用程序时,系统会询问用户是否赋予该权限。如果添加一个新的许可应用程序的更新版本或者新权限时,系统会询问用户。一旦用户安装了应用程序,可以撤销权限许可的唯一方法是卸载应用程序。

  很多时候,请求权限失败将导致SecurityException被抛出到应用程序。但是,这不能保证一定会发生。例如,sendBroadcast(Intent)作为数据被传递到每个Receiver,该方法调用之后返回,所以如果有权限失败你将不会收到一个异常。但是,在几乎所有情况下,一个失败的权限将被打印到系统日志。
  由Android系统提供的权限可以在Manifest.permission里找到。任何应用程序也可以定义并执行其自己的权限,所以这不是所有可能的权限的完整列表。
  特定权限可以被强制执行在程序的运行过程中的一些地方:

  • 在调用进系统的时候,阻止应用程序执行某些功能
  • 当启动一个Activity的时候,阻止应用程序启动其他程序的Activity
  • 发送和接收广播通知的时候,控制谁能接收到你的广播或是谁能给你发送广播
  • 当访问和操作某个content provider的时候
  • 绑定或是开启一个service的时候

自动权限调整
  随着时间的推移,新的限制可能被添加到该平台,使得在为了使用某些API,你的应用程序必须请求它以前没有需要的权限。由于现有的应用程序承担访问这些API是免费提供的,Android可以在manifest文件中申请新的许可请求,以避免破坏在新的平台版本的应用程序。 Android根据targetSdkVersion属性值的判断应用程序是否需要该权限。如果该值大于在其中允许加入的版本时,则Android添加许可。
  例如:WRITE_EXTERNAL_STORAGE权限是在API级别为4时引入用来限制访问共享存储空间。如果targetSdkVersion为3或更低,此权限将被添加到您的应用程序在较新版本的Android。
  注意:如果权限自动添加到你的应用程序,在google play的应用程序列表中列出了这些额外的权限,即使你的应用程序可能没有实际使用他们。
  为了避免这种情况,并删除不需要的默认权限,随时更新你的targetSdkVersion,使其尽可能的高。在Build.VERSION_CODES文档中你可以看到每个版本有哪些权限,添加哪些权限。
  
普通权限和危险权限
  系统权限分为几个保护级别。最重要的两个保护级别是普通权限,危险权限。

  • 普通权限是指你的应用程序需要访问应用程序沙箱外的数据或资源的地区,但那里有很少的风险,不会侵犯用户的隐私或影响其他应用程序的运行。例如,允许接通手电筒是一个普通权限。如果一个应用程序声明,它需要一个普通的权限时,系统会自动授予的权限的应用程序。
  • 危险的权限,包括那些涉及到用户的私人信息,或可能影响用户的存储的数据或其他应用程序的运行数据或资源的操作。例如,要读出的用户的联系人是一种危险的权限。如果一个应用程序声明它需要一个危险的权限,用户必须明确地授予该权限给该应用。

权限组
所有危险的Android系统权限属于权限组。如果设备运行的是Android6.0(API级别23)和应用程序的targetSdkVersion为23或更高,下面的系统行为适用于你的应用程序请求一个危险的权限:

  • 如果一个应用程序在其manifest文件中声明了所需要的危险的权限,那么该应用程序目前还没有权限组中的任何权限,系统会显示一个对话框,说明该应用程序要访问的权限组。该对话框不介绍该组中的特定权限。例如,如果一个应用程序请求READ_CONTACTS权限,系统对话框,只是说该应用程序需要访问该设备的联系人。如果用户同意,系统只给应用程序要求的权限。
  • 如果一个应用程序在其其manifest文件中声明了所需要的危险的权限,以及应用程序已经有相同的权限组中的另一个危险的权限,系统立即授予权限,而不与用户的任何干预。例如,如果一个应用程序先前已经请求并且被授予READ_CONTACTS权限,并且它随后请求WRITE_CONTACTS,系统立即授予该权限。

  如果设备运行的是Android5.1(API级别22)以下,或应用程序的targetSdkVersion是22或更低时,系统会要求用户在安装时授予权限。该系统只是告诉用户什么权限组的应用需求,而不是单独的权限。

声明和实施许可
想要实施自己的permissions,首先需要在manifest文件中利用一个或者多个<permissions>标签声明它。
例如,一个app想要控制其他app能够启动它的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>属性应该提供,label属性通过字符串来表示权限的标签,description属性是权限的详细描述。

在AndroidManifest.xml中实施许可
  用于限制进入系统或应用程序的Components的高级别许可可以再AndroidManifest.xml中实现.所有这些都可以通过在相应的component中包含 android:permission 属性,命名该permission以使其被用以控制进入的权限。
  Activity许可(应用于<activity>标签)限制了谁才可以启动相应的活动。permission会在Context.startActivity()Activity.startActivityForResult()的时候进行检查。如果caller没有所需的权限,则会抛出一个SecurityException。
  Service许可(应用于<service>标签)用于限制谁才可以start或bind该service。在Context.startService() , Context.stopService()Context.bindService()调用的时候会进行权限检查。如果caller没有所需的权限,则会抛出一个SecurityException。
  BroadcastReceiver许可(应用于<receiver>标签)用于限制谁才可以向该receiver发送广播。权限检查会在Context.sendBroadcast() 返回时进行,由系统去发送已经提交的广播给相应的Receiver。最终,一个permission failure不会再返回给Caller一个exception;它只是不会去deliver该Intent而已。同样地,Context.registerReceiver()也可以有自己permission用于限制谁才可以向一个在程序中注册的receiver发送广播。另一种方式是,一个permission也可以提供给Context.sendBroadcast() 用以限制哪一个BroadcastReceiver才可以接收该广播。
  ContentProvider许可(应用于<provider>标签)用于限制谁才可以访问ContentProvider提供的数据。(Content providers有一套而外的安全机制叫做URI permissions,这些在稍后讨论)不同于其它的Components,这里有两种不同的permission属性可以设置: android:readPermission 用于限制谁可以读取provider中的数据,而 android:writePermission 用于限制谁才可以向provider中写入数据。需要注意的是如果provider同时被读写许可,如果这时只有写许可并不意味着你就可以读取provider中的数据了。当你第一次获取provider的时候就要进行权限检查(如果你没有任何permission,则会抛出SecurityException), 并且这时你对provider执行了某些操作。当使用ContentResolver.query()时需要读权限,而当使用ContentResolver.insert(), ContentResolver.update(), ContentResolver.delete()时需要写权限。在所有这些情况下,没有所需的permission将会导致SecurityException被抛出。

发送广播时实施许可
  除了之前说过的Permission(用于限制谁才可以发送广播给相应的BroadcastReceiver),你还可以在发送广播的时候指定一个permission。在调用Context.sendBroadcast()的时候使用一个permission string,你就可以要求receiver的宿主程序必须有相应的permission。
  值得注意的是Receiver和broadcaster都可以要求permission。当这种情况发生时,这两种permission检查都需要通过后才会将相应的intent发送给相关的目的地。

其他权限实施
  在调用service的过程中可以设置任意细化的许可。这是通过Context.checkCallingPermission()方法来完成的。呼叫的时候使用一个想得到的permission string,并且当该权限获批的时候可以返回给呼叫方一个Integer(没有获批也会返回一个Integer)。需要注意的是这种情况只能发生在来自另一个进程的呼叫,通常是一个service发布的IDL接口或者是其他方式提供给其他的进程。
  Android提供了很多其他的方式用于检查permissions。如果你有另一个进程的pid,你就可以通过Context.checkPermission(String, int, int)去针对那个pid去检查permission。如果你有另一个应用程序的package name,你可以直接用PackageManager的方法PackageManager.checkPermission(String, String)来确定该package是否已经拥有了相应的权限。
  
URI许可
  到目前为止我们讨论的标准的permission系统对于内容提供器(content provider)来说是不够的。一个内容提供器可能想保护它的读写权限,而同时与它对应的直属客户端也需要将特定的URI传递给其它应用程序,以便对该URI进行操作。一个典型的例子是邮件应用程序的附件。访问邮件需要使用permission来保护,因为这些是敏感的用户数据。然而,如果有一个指向图片附件的URI需要传递给图片浏览器,那个图片浏览器是不会有访问附件的权利的,因为它不可能拥有所有的邮件的访问权限。
  针对这个问题的解决方案就是per-URI permission: 当启动一个activity或者给一个activity返回结果的时候,呼叫方可以设置Intent.FLAG_GRANT_READ_URI_PERMISSION和/或 Intent.FLAG_GRANT_WRITE_URI_PERMISSION。这赋予接收活动(activity)访问该意图(Intent)指定的URI的权限,而不论它是否有权限进入该意图对应的内容提供器。
  这种机制允许一个通常的能力-风格(capability-style)模型,以用户交互(如打开一个附件, 从列表中选择一个联系人)来驱动细化的特别授权。这是一个很关键的能力,可以减少应用程序所需要的权限,只留下和程序行为直接相关的权限。
  这些URI permission的获取需要内容提供器(包含那些URI)的配合。强烈推荐在内容提供器中实现这种能力,并通过android:grantUriPermissions或者标签来声明支持。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值