android 权限机制,你真的了解么

一、Android 的权限机制

Android 是目前最流行的智能手机软件平台之一,在智能移动终端如火如荼发展的同时,其安全态势也日益严峻。有调查表明,恶意软件的数量在持续的上升,Google 在 Android 安全机制上面也做了很多工作,并且一直在持续的更新,其 Android 的安全模型由 3 个部分组成:Linux 安全机制、Android 本地库及运行环境安全与 Android 特有的安全机制,如下图:

本文只涉及到其中的权限机制介绍,其他的部分如果有感兴趣的,我们可以后续一起探讨。
Android 的权限管理遵循的是 “最小特权原则”,即所有的 Android 应用程序都被赋予了最小权限。一个 Android 应用程序如果没有声明任何权限,就没有任何特权。因此,应用程序如果想访问其他文件、数据和资源就必须在 AndroidManifest.xml 文件中进行声明,以所声明的权限去访问这些资源。否则,如果缺少必要的权限,由于沙箱的保护,这些应用程序将不能够正常提供所期望的功能与服务。
所有应用程序对权限的申请和声明都被强制标识于 AndroidManifest.xml 文件之中,通过, ,等标签指定。如果需要申请某个权限,可以通过指定。应用程序申请的权限在安装时提示给用户,用户可以根据自身需求和隐私保护决定是否允许对该应用程序授权。

二、权限基本知识

2.1 权限的类别

由于基于 Linux 内核,Android 系统中的权限分为以下 3 类。

2.2 Protection level

我们经常在 AndroidManifest 中使用权限,如果我们想让应用程序可以发短信,那么应该这样写:


其权限的定义是在 frameworks/base/core/res/AndroidManifest.xml 中,如下:
android:permissionGroup="android.permission-group.COST_MONEY"
android:protectionLevel="dangerous"
android:label="@string/permlab_sendSms"
android:description="@string/permdesc_sendSms" />

这个 XML 可以认为是系统 APK 使用的 AndroidManifest.xml,该 APK 使用系统的私钥进行签名。
下面分别简单介绍下各个标签的含义:
android:name:权限的名字,uses-permisson 使用的。
android:permissionGroup:权限的分类,在提示用户安装时会把某些功能差不多的权限放到一类。
android:protectionLevel:分为 Normal、Dangerous、Signature、SignatureOrSystem。
android:label:提示给用户的权限名。
android:description:提示给用户的权限描述。
其中 android:protectionLevel 各个属性说明如下:

2.3 进程的权限表现

Android 是一个多进程系统,在这个系统中,应用程序会在自己的进程中运行,系统和应用之间的安全性是通过 Linux 进程级别来强制实现的,会给应用程序分配 user ID 和 Group ID。
比如我们查看 qqdownload 这个进程,adb shell 后查看下其进程 id(红色部分):

根据 id,执行下查看状态,如下:

我们关注如下三行:

Uid: 10301 10301 10301 10301

Gid: 10301 10301 10301 10301

Groups: 1006 1013 1015 1028 3001 3002 3003 9997 50301
!
这里我们便看到了系统进程的权限配置信息,这里的数字具体代表意义,可以在
Android\system\core\include\private\android_filesystem_config.h 里面看到,其部分内容如下:

其中 qqdownload 对应的 Groups 描述如下:

1006 camera devices

1013 mediaserver process

1015 external storage write access

1028 external storage read access

3001 bluetooth: create any socket

3002 bluetooth: create sco,rfcomm or l2cap sockets

3003 can create AF_INET and AF_INET6 sockets

9997 shared between all apps in the same profile

50301 start of gids for apps in each user to share

不同的手机这个信息显示的不一样,这个是 vivo 手机的信息,在华为手机上只有 3001 3002 3003 9997 50689。

我们拿其中一个写 SD 卡的权限来简单说明一下:
写 SD 卡权限是



我们看一个重要的文件,frameworks\base\data\etc\Platform.xml 里的内容:
<permission name="android.permission.WRITE_EXTERNAL_STORAGE" >
<group gid="sdcard_rw" />
</permission>

在看下图:

对于申请了 WRITE_EXTERNAL_STORAGE 特权的应用,该应用的进程的 gids 就包含了 sdcard_rw,就可以对 sd 卡中的文件进行操作了。
再看 Android_filesystem_config.h 的源码:
define AID_SDCARD_RW 1015 /* external storage write access */

我们看到 qqdownload 进程的 Groups 里面有 gid 为 1015,1015 就是对应 sdcard_rw。

以上介绍了进程的权限表现,实际工作中,我们可能不需要关注这些。

2.4Android 系统对应用程序权限申请的处理方式分析

Android 系统对应用程序授权申请的处理流程:

具体如下图:

Android 用户权限赋予示意图

2.5 Android 原生权限管理:AppOps

2.5.1AppOps 简介

AppOps 全称是 Application Operations,类似我们平时常说的应用程序的操作权限管理,AppOps 是 Google 原生 Android 包含的功能,但是 Google 在每次版本更新时都会隐藏掉 AppOps 的入口,Google 高管 Hiroshi Lockheimer 的原话:“App ops 发布的时机不太对头,我们需要全面解决问题,而不是单独地发布 App ops”。

在今年的 Google IO 大会上,Google 透露 Android M ( Android 6.0 ) 会加入 Application Permission Manage 的功能,该功能应该就是基于 AppOps 实现的。

注意:AppOps 虽然涵盖了 App 的权限管理,但是 Google 原生的设计并不仅仅是对 “权限” 的管理,而是对 App 的 “动作” 的管理。我们平时讲的权限管理多是针对具体的权限(App 开发者在 Manifest 里申请的权限),而 AppOps 所管理的是所有可能涉及用户隐私和安全的操作,包括 access notification,,keep weak lock,activate ***,display toast 等等,有些操作是不需要 Manifest 里申请权限的。

2.5.2 功能效果

AppOps 的权限设置是在系统的 Settings App 里,Settings -> Security -> AppOps。

点击某一 app,可以查看该 app 的权限管理详情,也可以设置显示。

AppOps 默认给用户提供了两个设置选项:

允许该项权限/禁止该项权限

而其实代码逻辑里,有三种可选项:

允许/禁止/提示

用户选择 “提示” 选项,则该 app 在执行这一操作时,系统会给用户相应的提示,待用户选择后 app 继续执行。

2.5.3AppOps 总体概览

AppOpsService

系统服务,系统启动时该服务会启动运行。

参考以下 ActivityManagerService.java,ActivityManagerService 启动过程中:

Appops.xml 位于 /data/system/目录下,存储各个 app 的权限设置和操作信息。

Appops_policy.xml 位于 /system/etc/目录下,该文件只在 appops strict mode enable 时才会存在和使用。

-API 接口: AppOpsManager

AppOpsService 实现了大部分的核心功能逻辑,但它不能被其他模块直接调用访问,而是通过 AppOpsManager 提供访问接口。

上传 UI 显示以及基本逻辑处理。

2.5.4 结构图

AppOps 整体的工作框架基本如下:

Setting UI通过 AppOpsManager 与 AppOpsService 交互,给用户提供入口管理各个 app 的操作。

AppOpsService具体处理用户的各项设置,用户的设置项存储在 /data/system/appops.xml 文件中。
AppOpsService 也会被注入到各个相关的系统服务中,进行权限操作的检验。
各个权限操作对应的系统服务(比如定位相关的 Location Service,Audio 相关的 Audio Service 等)中注入 AppOpsService 的判断。如果用户做了相应的设置,那么这些系统服务就要做出相应的处理。
(比如,LocationManagerSerivce 的定位相关接口在实现时,会有判断调用该接口的 app 是否被用户设置成禁止该操作,如果有该设置,就不会继续进行定位。)

2.5.5 相关 API 接口

尽管在 Android SDK 里能够看到部分 AppOps 的 API 接口,但是 Google 对此解释的很清楚:
This API is not generally intended for third party application developers; most features are only available to system applications。Obtain an instance of it throughContext。getSystemService withContext。APP_OPS_SERVICE。

即是说,这些 API 不是让第三方 app 使用的,而是供系统应用调用的。
使用 Android SDK 开发应用,如果要调用这些 API 的话,也会编译不通过。
但是想使用的话,可以尝试把 Android 源码里 AppOpsManager.java 打包一下,把 jar 包导入自己的工程,就可以使用了。

部分重要的 API 接口如下:

int checkOp(String op,int uid,String packageName) Op 对应一个权限操作,该接口来检测应用是否具有该项操作权限。
int noteOp(String op, int uid, String packageName) 和 checkOp 基本相同,但是在检验后会做记录。
int checkOpNoThrow(String op,int uid,String packageName)
和 checkOp 类似,但是权限错误,不会抛出 SecurityException,而是返回 AppOpsManager。MODE_ERRORED。

int noteOpNoThrow(String op,int uid,String packageName)
类似 noteOp,但不会抛出 SecurityException。
void setMode ( int code,int uid,String packageName,int mode)这个是我们最需要的方法,改变 app 的权限设置,但偏偏被 google 隐藏了。
code 代表具体的操作权限,mode 代表要更改成的类型(允许/禁止/提示)

正常情况下(如果 OEM 厂商没有做特殊处理),把 AppOpsManager.java 打包,引入 jar 包到工程内,是可以使用上述 API 接口的,
也即是可以自行设计 UI,提供入口来改变 app 权限。
具体权限对应的 code,可以查看 AppOpsManager.java 源码里的描述。

三、权限变化趋势

Android M 之前,应用的权限请求是在安装时提示,确认后权限就会拥有。

但 Android M 出来后,将这个权限在运行时做了进一步的检查,用户随时可拒绝权限。

● 从平台角度看:Android 权限集不断扩展,但不是以提供更细粒度的权限为目标,而是为访问新的硬件功能提供安全保障。特别地,Dangerous 权限集的数量也在不断增多;

● 从第三方应用和预装应用角度看:大量应用并未遵守最小特权原则,而是存在大量过多申请权限的情形。值得注意的是:许多预装应用使用大量高级别的权限,带来很大的安全隐患。

用户只有通过不断学习,充分理解新加入的权限说明,才能在安装软件时从 Android 权限警告中获取足够的信息,从而做出正确的决定。鉴于 Android 系统每隔数月就有较大的版权更新,并引入较多新的权限,这为用户提出了很高要求。

四、Android M 变化以及带来的影响

从 Android 6.0(API level 23)开始,用户对应用权限进行授权是发生在应用运行时,而不是在安装时。这样可以让用户在安装时节省时间,而且可以更方便的控制应用的权限(至少权限管理不需要 root 了)。用户可以按照对应用的需求来控制应用的权限,比如百度地图的联系人权限。同时用户也可以在应用程序设置中撤销对应用的权限授权。
Android 系统中的权限被划分为两类:普通权限和敏感权限(更多普通权限、敏感权限及权限组信息:
普通权限不会涉及到用户隐私,如果应用在 manifest 文件中直接声明了普通权限,系统会自动授予权限给应用。比如:网络 INTERNET、蓝牙 BLUETOOTH、震动 VIBRATE 等权限。
敏感权限则要获取到一些用户私密的信息。如果你的应用需要获取敏感权限,首先需要获取用户的授权。比如:相机 CAMERA、联系人 CONTACTS、存储设备 STORAGE。
https://developer.android.com/reference/android/Manifest.permission.html
详情见上面这个链接,每个权限里面可能会有 Protection level,标记着是 dangerous 还是 normal


在 Android 的各个版本中,不论是普通权限还是敏感权限,都需要在 manifest 文件中声明,例如权限声明。然而,在不同版本的操作系统或不同的 target SDK level 中的结果是不同的。
如果设备运行 Android 5.1 或者更低版本的操作系统,或者你的目标 SDK 版本号小于或等于 22,当你在 manifest 文件中请求了一些权限,用户必须在安装过程时授予全部权限,否则应用不能正常安装。
如果设备运行在 Android 6.0 或者更高版本,并且目标 SDK 版本号大于或等于 23,应用程序必须要在 manifest 文件中声明需要的权限,当程序运行时,它必须要向用户请求授权每个所需的敏感权限。用户可以允许或拒绝每个权限,并且程序可以依赖用户已经授权的权限继续运行。(这里可能比较绕,举个例子:假设你的 app 需要联系人和拍照权限,在请求权限时用户只授予了联系人权限,那么当前程序可以正常运行并获取联系人信息,但是无法进行拍照)
注:本篇文章讲解如何在 API level 23 或更高版本并且设备版本为 Android 6.0 或者更高。如果 app 的 targetSdkVersion 为 22 或者更低,系统会在安装或者更新程序时提示用户授权所有敏感权限。
这里介绍下几个常量:

targetSdkVersion:是在程序运行的时候起作用,用于提高指定版本的设备上程序运行体验。

minSdkVersion 和 maxSdkVersion:是在程序安装的时候起作用,用于指定哪些版本的设备可以安装此应用。

target API leve:是在编译的时候起作用,用于指定使用哪个 API 版本 (SDK 版本) 进行编译。

4.1 PROTECTION_NORMAL 类权限

当用户安装或更新应用时,系统将授予应用所请求的属于 PROTECTION_NORMAL 的所有权限(安装时授权的一类基本权限)。这类权限包括:

只需要在 AndroidManifest.xml 中简单声明这些权限就好,安装时就授权。不需要每次使用时都检查权限,而且用户不能取消以上授权。

4.2 权限组

权限被分组了,如下表:

同一组的任何一个权限被授权了,其他权限也自动被授权。例如,一旦 WRITE_CONTACTS 被授权了,app 也有 READ_CONTACTS 和 GET_ACCOUNTS 权限了。

4.3 检查权限

如果你的程序需要敏感权限,那么你必须在每次调用需要该权限的方法时都需要检查权限。因为用户随时都可能会对你程序的某些权限取消授权,所以即使你的应用昨天使用过相机,你也无法确定今天是否还有这个权限。

你可以通过 ContextCompat.checkSelfPermission() 方法来验证你的应用是否拥有某个权限。比如,下面的代码段是检查是否有拥有写日历权限:

4.4 请求权限

如果你的应用需要敏感权限并且这些敏感权限已经在 manifest 文件中声明,一定要询问用户获取权限。Android 系统提供了几种请求权限的方法。调用这些方法后,系统会弹出一些 Dialog(无需用户自定义)。

4.5 解释需要权限的原因

在一些应用场景下,你可能想要让用户知道需要获取某个权限的原因。例如,如果用户使用相册应用,用户可能会理解这个应用会需要相机权限,但是用户可能不会理解为什么相册应用还需要获取位置或者联系人。在你请求获取权限之前,你应该考虑提示用户。切记不要使用大量解释;如果你解释的内容过多,用户可能会觉得你的应用比较烦人,可能会卸载你的应用…(这段翻译可能有点问题…)

如果你需要的权限已经被用户拒绝过一次权限请求,当用户再次使用需要获取权限的功能时,应用程序最好向用户解释需要对应权限的原因。因为如果用户一直尝试使用需要权限的功能,却一直没给为该功能对应的权限,说明用户还没有明白为什么应用程序需要这个权限来实现这个功能。在这种情况下可能需要提示用户需要权限的原因。

Android 系统提供了 shouldShowRequestPermissionRationale() 方法来帮助开发者判断是否需要向用户解释需要权限的原因。当某条权限之前已经请求过,并且用户已经拒绝了该权限时,shouldShowRequestPermissionRationale () 方法返回的是 true

注意:如果用户拒绝某条权限,并且在提示授权的窗口中勾选了不再提示选项时,shouldShowRequestPermissionRationale () 的返回值为 false。当某些设备禁止应用程序获取某些权限时,shouldShowRequestPermissionRationale () 也会返回 false

4.6 向用户请求获取应用程序需要的权限

如果你的应用程序没有获取到它需要的权限,那么应用程序需要调用该权限对应的 requestPermissions() 方法,调用 requestPermissions() 方法时需要传入一个请求码(requestCode),这时系统会弹出一个对话框让用户选择是否授权,用户选择后,在回调方法 onRequestPermissionsResult() 中返回对应的请求码(requestCode)和授权结果。

下面这段代码 检查应用程序是否有读联系人权限,在未获取读联系人授权时请求获取该权限 (完整示例见 Android_M_Permission):


注意:当应用程序调用 requestPermissions() 方法时,系统会弹出一个对话框给用户。应用程序不能设置或更改该对话框,如果应用程序需要提供一些信息或者向用户解释,需要在调用 requestPermissions() 方法之前。

4.7 处理请求权限的结果

当应用程序请求获取权限时,系统会弹出一个对话框给用户。当用户点击某个选项时,系统会调用 onRequestPermissionsResult() 方法来传递用户的选择结果。应用程序需要重写 onRequestPermissionsResult() 方法来判断用户是否对相应权限授权。。这个回调方法会传递一个与 requestPermission() 方法相同的 requestCode。例如,应用程序请求 READ_CONTACTS 方法,它将会有如下的回调方法:

授权的对话框显示的是系统描述的权限组(permission group),它没有显示列出详细的权限列表。比如,如果你请求 READ_CONTACTS 权限,系统对话框只会提示用户应用程序需要获取联系人权限,用户只需要给每个权限组授权一次。如果应用程序请求获取一个权限组的其他权限(在 manifest 文件中声明的权限),系统会自动授予该权限。当你请求这个权限时,系统会调用 onRequestPermissionsResult() 回调方法并且传递 PERMISSION_GRANTED,这跟用户在弹窗中点击授予权限的按钮的流程是相同的。

注意:应用程序还是需要明确的请求它所需要的每个权限,即使用户已经授予了跟这个权限在同一个 permission group 的其他权限。
除此之外,对某个权限组的授权可能会改变。程序的代码不能依赖于用户已经对某个权限组授权的假设

例如,应用程序在 manifest 文件用声明了 READ_CONTACTS 和 WRITE_CONTACTS 权限,如果应用程序请求了 READ_CONTACTS 权限并且用户授予了该权限,那么当应用程序请求 WRITE_CONTACTS 权限时,系统会自动授予应用程序该权限。

译者注:READ_CONTACTS 和 WRITE_CONTACTS 都属于 CONTACTS 权限组。更多关于权限组信息可以访问 permission group 或直接看我的截图:权限和权限组
   如果用户拒绝了一个应用权限请求,那么应用程序应该进行适当的操作。例如:应用程序可以弹出一个对话框来解释为什么用户不能执行需要该权限的操作。当系统提示用户给应用程序授权权限时,会给用户提供一个不再提示的选项来通知系统不再针对该权限进行询问。用户勾选该选项后,当应用程序请求获取对应权限时,系统会立即拒绝授权。系统会调用 onRequestPermissionResult() 回调方法并且传递 PERMISSION_DENIED 参数,就像用户拒绝授权一样。这意味着,当你调用 requestPermissions() 方法时,你不能假定应用程序会跟用户直接交互。

本章完~


TMQ(腾讯移动品质中心)是腾讯最早专注在移动 APP 测试的团队
网站专注于移动测试技术精华,饱含腾讯多款亿级 APP 的品质秘密,文章皆独家原创,我们不谈虚的,只谈干货!】

扫一扫 关注 TMQ
精彩分享不断


↙↙↙阅读原文可查看相关链接,并与作者交流