Android 6.0 应用权限(一) -- 系统权限(System Permissions)

原文链接:https://developer.android.com/intl/zh-cn/guide/topics/security/permissions.html

安卓是一个权限分离的操作系统,每个应用在里面使用独立的身份(Linux用户ID和群组ID)运行。部分系统也分成独立的身份。Linux因此将应用与其他应用和系统独立开来。

其它更细粒度的安全特性通过“permission”机制来支持。包括,强制限制特定线程要执行的特殊操作,为允许临时访问特定的数据片段而授权的每个URI权根。

这篇文档描述了应用开发者如何使用安卓提供的安全特性。更通用的安卓安全总结在AOSP中提供。

安全架构

安卓安全架构的中心设计思想是默认无任何应用享有任何可能对其他应用,操作系统或用户产生不利影响的操作的权限。这个包括读取或写入用户的私有数据(比如联系人或邮件),读取或写入别的应用的文件,执行网络访问,使设备保持唤醒等。

因为每个安卓应用在一个线程沙箱中执行,应用必须显性地分享资源和数据。他们通过声明他们需要的权限来达到数据共享,从而获得基础沙箱没有提供的额外的的性能。应用静态地声明他们需要的权限,安卓系统提示用户来同意授权。

这里讲的沙箱不依赖于用来构建应用的技术。尤其Dalvik VM不是一个安全边界,任何应用都可以运行本地代码。所有类型的应用--Java, native, 和hybrid-- 被以相同的方式沙箱化并且彼此具有相同的安全程度。

应用签名

所有的APK(.apk文件)都必须使用一个证书签名,这个证书的私有密钥由他们的开发者所掌握。这个证书用来识别这个应用程序的作者; Android应用程序使用自签名的证书是完全允许的和典型的用法。在安卓里面使用证书的目的是用来区分应用的作者。这个允许系统去授权或者拒绝应用程序访问签名级别权限,并且允许系统授权或者拒绝应用作为另外的应用被授予相同的Linux身份。

用户身份和文件访问

在安装的时候,Android给予每个包一个独特的Linux用户ID, 这个身份在这个包的生命周期内永久保留在安装设备上。在一台不同的设备上,相同的包可能会有不同的UID; 重要的是,每个包在指定的设备上都有一个不同的UID.

因为安全增强发生在线程级别, 任意两个包的代码不能正常地运行在同一个线程中,因此他们需要作为不同的Linux用户来运行。你可以使用每个包的AndroidManifest.xml的manifest标签的sharedUserId属性来让他们指定相同的用户ID.通过这样做,出于安全的考虑,这两个包被当作同一个应用来对待,拥有相同的UserID和文件权限。需要指出的是,为了保证安全,只有两个使用了相同签名(且sharedUserID一致)的应用才会拥有相同的user id.

被应用存储的任意数据将被分配那个应用的用户ID, 并且一般不能被其他的包正常访问。当使用getSharedPreferences(String, int), openFileOutput(String, int), 或者openOrCreateDatebase(Strng, int, SQLiteDatabase.CursorFactory)创建了一个新文件, 你可以使用MODE_WORLD_READABLE 和/或MODE_WORLD_WRITEABLE标志来允许任意其他的包来读写这个文件。 在设置这些标志的时候,这个文件仍被你的应用所拥有,但是它的全局读写权限已经被设置因而任何应用都可以见到它。

使用权限

一个基础的Android应用默认没有任何权限绑定,即它不能做任何对用户体验和设备数据产生不良影响的动作。为了使用受保护的设备的特性,你必须在你的app manifest中包含一个或更多的<uses-permission>标记。

举个例子,一个需要监控新来的短消息的应用需要详细说明:

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


如果你的应用在它的清单文件中列出了普通权限(也就是说,那些不会对用户隐私或设备的操作造成风险的权限),系统会自动授予这些权限。如果你的应用列出了一些危险的权限(dangerous permissions,即那些潜在的可能影响用户隐私或者设备正常操作的权限)系统要求用户明确授予这些权限。Android发出这些请求的方式依赖于当前的系统版本和你的应用指定的系统版本。

·        如果这个设备运行的是Android 6.0(API level 23) 或者更高的版本, 并且这个应用的targetSdkVersion是23或者更高, 这个应用在运行时向用户请求权限。用户可以在任意时刻调用需要的权限,所以应用需要每次运行时都检查它是否具有需要的权限。如何检查,请看下文。

·        如果设备运行在Android 5.1(API level 22)或者更低的版本,或者当前应用的targetSdkVersion是22或者更低,系统在用户安装这个应用的时候要求用户进行授权。一旦用户安装了这个应用,他们用来撤消权限的唯一方式就是卸载这个应用。

很多时候,一个权限失败将会导致一个SecurityException 被抛回应用。但是,这个不保证会发生在任何地方。比如,sendBroadcast(Intent)方法是在数据被传递到每个receiver的时候检查权限,在这个方法的调用返回之后,所以就算有权限失败你也不会接收到异常。然而,在几乎所有的场景中,权限失败都会在系统log中打印出来。

这些被Android系统提供的权限可以在Manifest.permission中查看到。任意的应用可能也会定义和加强它自己的权限,因此,这个不是一个全面的列出了所有可能的权限的列表。

在你的程序的操作期间,一个特定的权限可能在许多地方被执行。

  • 在调用进入系统的时候,为了防止应用执行一些功能
  • 当启动一个activity的时候,为了防止应用启动其他应用的activity
  • 在发送和接收广播的时候,为了控制可以接收你的广播的接收器或给你发送广播的发送者
  • 当访问和操作一个content provider的时候
  • 创建或者启动一个服务的时候

自动化权限调整

随着时间的推移,新的限制或许会被添加到平台,比如,为了使用特定的api, 你的应用必须请求一个以前并不需要的权限。因为当前的应用假定访问这些api是可行的,安卓可能在这些应用的manifest中申请了新的权限请求来防止在新的平台上面破坏这些应用。Android决定关于是否需要权限基于targetSdkVersion属性的值。如果这个值低于这个权限被要求添加的版本的值,然后安卓就自动添加这些权限。

比如,WRITE_EXTERNAL_STORAGE权限在API level 4的时候被添加用来限制对共享存储空间的访问。如果你的targetSdkVersion是3或者更低,你的应用会自动添加这些更新的安卓版本的权限。

注意: 如果一个权限被自动地添加到你的应用,你的应用在Google Play上列出了这些附加的应用即使你的应用可能实际并不需要他们。

为了避免这种情况和移除掉默认的你不需要的权限的情况,经常去把你的targetSdkVersion更新到尽可能高。你可以在Build.VERSION文档(https://developer.android.com/intl/zh-cn/reference/android/os/Build.VERSION_CODES.html)中查看安卓的每个发布都添加了哪些权限。

普通权限和危险权限(Normal and DangerousPermissions)

系统权限被分为几个保护级别。最重要的两种被广泛了解的保护级别是普通权限和危险权限:

·        普通权限(Normal Permissions)覆盖的区域包括你需要用来访问这个应用的沙箱外的数据或资源的情况,对用户的隐私或其它应用的操作几乎没有影响的情况。例如,设置时区的权限就是一个普通权限。如果应用声明它需要一个普通权限,系统公自动地授权这个普通权限给这个应用。想要查看当前的所有的普通权限的列表,可以在Normal permissions. (https://developer.android.com/intl/zh-cn/guide/topics/security/normal-permissions.html)查看。

·        危险权限(Dangerous permissions)覆盖的范围包括:应用想要使用涉及到用户隐私信息的数据或资源的情况或者能够暗中影响用户存储的数据或其它应用的运行的情况。如果应用程序声明它需要一个危险权限,用户必须明确地对应用进行授权。

权限组

所有的危险的安卓系统权限属于权限组。如果设备运行在Android 6.0(API level 23)并且应用的targetSdkVersion是23或者更高,当你的应用请求一个危险权限时,下列系统行为将会应用:

·        如果一个应用请求一个在它的manifest清单中列出的危险权限,并且这个应用当前没有任意一个权限组的权限,系统会给用户显示一个对话框来描述这个应用想要访问的权限组。比如,如果一个应用请求READ_CONTACTS权限,系统对话框只会提示,这个应用需要访问设备的联系人。如果用户批准授权,系统仅给应用授权它请求的权限。

·        如果一个应用请求一个在它的manifest清单中列出的危险权限,并且这个应用已经拥有同一个权限组中的其它危险权限,系统会立即授权而不会再与用户有任何交互。比如,如果一个应用之前已经请求并被授权过READ_CONTACTS权限,那它再请求WRITE_CONTACTS权限时,系统会立刻授予它这个权限。

任意权限都可以归纳到一个权限组,包括普通权限和那些由你的应用所定义的权限。但是,如果这个权限是危险的,它所在的权限组仅会影响这个用户的用户体验。你可以忽视普通权限的权限组。

如果设备运行在Android 5.1(API level 22) 或者更低的版本,或者应用的targetSdkVersion22或者更低,系统会要求用户在安装应用的时候进行授权。系统只会告诉用户这个应用需要哪个权限组,而不是具体地哪个权限。

1. 危险权限和权限组

Permission Group

Permissions

CALENDAR

CAMERA

CONTACTS

LOCATION

MICROPHONE

PHONE

SENSORS

SMS

STORAGE

定义和执行权限


为了执行你自己的权限,首先你必需在你的AndroidManifest.xml中使用一个或多个<permissions> 标签来声明它们。

比如,一个想要控制可以启动它的activity的对象的应用可以对这个操作定义以下权限:

<manifestxmlns:android="http://schemas.android.com/apk/res/android"
    package="com.me.app.myapp">
    <permissionandroid: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中列出的)或者在极少数情况下设置为你自己定义的一个组。最好是使用一个存在的组,因为这个可以简化向用户展示的权限UI。

需要指出的是,label和description都应用被加入到一个权限声明中,这些词条资源可以在用户浏览权限(android:label)列表的时候或者单一权限(android:description)的详细信息的时候被显示出来。这个label应用短一些,只用少量的词汇来描述这个权限所保护的功能片段。这个description应用是一行描述这个权限允许其持有者可以进行哪些操作的句子。我们对这些描述的惯例一般是两个句子,第一行描述这个权限,第二行警告用户如果这些权限被授予后可能发生哪些不好的事情。

这儿有一个CALL_PHONE权限的label和description的例子:

<stringname="permlab_callPhone">directly call phone numbers</string>
    <stringname="permdesc_callPhone">Allows the application to call
        phone numbers without your intervention. Maliciousapplications may
        cause unexpected calls on your phone bill. Notethat this does not
        allow the application to call emergency numbers.</string>

你可以通过设置应用或者shell命令:adb shell pm list permissions来查看这些权限。使用设置应用来查看,路径如下: Settings > Applications. 选择一个应用,向下滑动会看到thepermissions that the app uses. 对开发者而言,adb '-s' 选项会将权限以表格的形式显示出来,类假于用户看到它们的形式。

$ adb shell pmlist permissions -s
AllPermissions:

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>标签里)限定谁可以启动或者绑定关联的服务。这个权限在Context.startService(), Context.stopService() 和 Context.bindService(); 如果调用者没有指定的权限,会抛出一个安全异常。

BroadCastReceiver权限(应用在<receiver>标签里)限制谁可以发送广播给相关的接收器(receiver). 这个权限在Context.sendBroadcast()返回后,当系统尝试传递提交的广播给指定的接收器时进行检检查。因此,一个权限失败不会导致向调用者抛出异常。它仅仅会中止传递intent. 以同样的方式,权限可以提供给Context.registerReceiver(),以控制谁可以广播到程序化注册的receiver。另一种方式是,一个权限可以在调用Context.sendBroadcast()时被提供用来限制哪个BroadcastReceiver对象会被用来接收广播。

ContentProvider权限(应用在<provider>标签里)限制可以访问ContentProvider中的数据的访问者。(ContentProvider有一个重要的附加的安全设备可用于它们,称作 URI 权限,随后会描述到)不像其它的组件,有两个独立的权限属性你可以设置:android:readPermission 约束谁可以从这个provider里面读取内容;android:writePermission 约束谁可以向provider里面写入内容。需要注意的是,如果一个provider里面同时拥有读和写的权限,那么,如果仅拥有写的权限并不意味着你可以从其中读取内容。当你首次获取一个provider并对其执行操作时,会对这个权限进行检查(如果你没有获取这些权限,一个SecurityException将会被抛出来)。使用 ContentResolver.query() 需要获得 read 权限;使用 ContentResolver.insert(), ContentResolver.update(),ContentResolver.delete() 需要 write 权限。在这些所有的情况里, 没有获取到需要的权限的话,将会抛出 SecurityException. 

在发送广播时执行权限(Enforcing Permissions when Sending Broadcasts)除了强制谁可以向一个注册过的 BroadcastReceiver 发送 Intents外(如上所述), 你也可以指明一个在发送广播的时候需要的权限。通过调用带有一个权限词条(Permissio String)的 Context.sendBroadcast() ,你要求接收器的应用为了收到你的广播必须持有需要的权限。

需要指出的是,无论是接收器还是广播都需要权限。当这种情况发生时,将要被传递给相关目标的Intent必须能通过权限检测。

其它的会执行的权限(Other Permission Enforcement)任意粒度的权限在一个服务内被调用的时候会被执行,这个由Context.checkCallingPermission()方法来完成。 调用一个需要的权限字串,它将返回一个整形值来指示这个权限是否被授权给当前的调用进程。注意,这个仅当你正在执行一个来自其它进程的调用的时候会被使用,通常会通过一个由service公开的IDL接口或其它进程提供的一些方式来使用。

URI权限 (URI Permissions)到目前为止,我们描述过的标准的权限系统在面对使用content providers的场景的时候通常还不足够完善。一个 contentprovider 可能想要使用读写权限来保护自身, 而其直接的客户也需要处理一些对他们需要操作的特定的指向其他应用的URI.  邮件应用的附件就是一个典型的例子。访问邮箱应用受权限的保护, 因为这个属于敏感的用户数据。然而, 如果一个图片附件的URi被给到一个图片浏览器(an image viewer), 这个图片浏览器不会获得打开附件的权限,因为它没有理由去持有一个可以访问所有邮件的权限。

这个问题的解决方式是单个的URI权限(per-URI permissions): 当启动一个 activity 或 返回一个结果给 activity时, 调用者可以设置 Intent.FLAG_GRANT_READ_URI_PERMISSION和/或Intent.FLAG_GRANT_WRITE_URI_PERMISSION。这个授权给接收信息的 activity 访问这个Intent中特定的数据的URI的权限, 不管它是否具有访问响应这个Intent的 content provider 数据的权限。

这个机制允许一个公共能力模型,其中,用户交互(打开一个附件,从列表中选择一个联系人,等等)驱动细粒度权限的临时授权。这是用来减少应用与这些行为直接发给关联的权限的关键技术。

这个细粒度URI权限的授权也要求持有这些URI的content provider的合作,强烈建议内容提供者继承这些特性,并且声明,他们通过android:grantUriPermissions 属性或 <grant-uri-permissions> 标记来支持这种方式。 


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值