开头不得不吐槽下:简书的markdown编辑器居然不支持目录索引,非常的坑.

安卓的安全和权限架构

先来看下google官方文档中对android安全架构的介绍:

Android 安全架构的中心设计点是:在默认情况下任何应用都没有权限执行对其他应用、操作系统或用户有不利影响的任何操作。这包括读取或写入用户的私有数据(例如联系人或电子邮件)、读取或写入其他应用程序的文件、执行网络访问、使设备保持唤醒状态等。

由于每个 Android 应用都是在进程沙盒中运行,因此应用必须显式共享资源和数据。它们的方法是声明需要哪些权限来获取基本沙盒未提供的额外功能。应用以静态方式声明它们需要的权限,然后 Android 系统提示用户同意。

应用沙盒不依赖用于开发应用的技术。特别是,Dalvik VM 不是安全边界,任何应用都可运行原生代码(请参阅 [Android NDK](https://developer.android.google.cn/tools/sdk/ndk/index.html))。各类应用 — Java、原生和混合 — 以同样的方式放在沙盒中,彼此采用相同程度的安全防护。

可以看出 android安全架构的两个中心点就是 权限 和 沙盒模型.

沙盒模型 属于进程级别的安全防护,它设置进程级别的隔离带来保证进程间互不影响.而权限 则属于开发者可以在应用层控制的属性,google设计权限机制 就是为了让开发者带上脚链跳舞,在授予应用开发者申请权限的能力的同时,又极力限制开发者对权限的滥用,避免app的恶意行为.

permission的三个重要性级别

为了后面便于分析permission的重要性级别与 android:permission 之间产生的化学反应,首先列出permission的三个重要性级别

protectionlevel.png

permission的危险级别由 android:protectionlevel 这项属性来决定.它一共有四个级别:

normal

The default value. A lower-risk permission that gives requesting applications access to isolated application-level features, with minimal risk to other applications, the system, or the user. The system automatically grants this type of permission to a requesting application at installation, without asking for the user's explicit approval (though the user always has the option to review these permissions before installing).

正常级别的权限属于最低危险等级的权限级别.这类权限不需要用户通过对话框显式地同意,即只要开发者在manifest里申请了权限,系统就会自动授予app这些权限.

dangerous

A higher-risk permission that would give a requesting application access to private user data or control over the device that can negatively impact the user. Because this type of permission introduces potential risk, the system may not automatically grant it to the requesting application. For example, any dangerous permissions requested by an application may be displayed to the user and require confirmation before proceeding, or some other approach may be taken to avoid the user automatically allowing the use of such facilities.

危险级别的权限通常具有访问用户隐私数据的属性(例如读取手机状态信息,收发短信,拨打电话),所以这类权限通常不会被自动授予app.

signature

A permission that the system grants only if the requesting application is signed with the same certificate as the application that declared the permission. If the certificates match, the system automatically grants the permission without notifying the user or asking for the user's explicit approval.

签名级别的权限:文档里是这样解释的: 只有当请求该权限的application具有与声明权限的application相同的证书时,这项权限才会被授予.如果证书相同,这项权限会被自动授予而不用显式地通知用户或征求用户同意.

和标签

Paste_Image.png

先来看看 标签的定义, 它用于定义权限,主要属性有两个,除了 protectionLevel 这个属性以外,还有就是 permissionGroup 这个属性,它将不同权限定义在一个权限组中.关于权限组,需要知道的一点是当某个权限组中包含了某个 Dangerous-Permission 的时候,如果用户同意了使用该危险权限,那么app会自动被授予使用该用户组中其它权限的权力.

public static final class permission_group {
public static final String CALENDAR = "android.permission-group.CALENDAR";
public static final String CAMERA = "android.permission-group.CAMERA";
public static final String CONTACTS = "android.permission-group.CONTACTS";
public static final String LOCATION = "android.permission-group.LOCATION";
public static final String MICROPHONE = "android.permission-group.MICROPHONE";
public static final String PHONE = "android.permission-group.PHONE";
public static final String SENSORS = "android.permission-group.SENSORS";
public static final String SMS = "android.permission-group.SMS";
public static final String STORAGE = "android.permission-group.STORAGE";
public permission_group() {
throw new RuntimeException("Stub!");
}
}

这里把安卓中所有的用户组列出来,类别有日历,相机,联系人,定位,MicroPhone,通话,传感器,短信,存储 这几个类别,都是涉及用户隐私的相当敏感的权限.

这个标签用的比较多了,就是在application标签下声明要获取某项权限.

这个标签用的特别少,,我在google上搜索了一圈也没见到有详细的介绍

,大概的用法是: 可以通过PackgeManager.addPermission来动态添加某个权限加入到 permission-tree 中,前提是在相同的package中,也就是包名相同,这个方法的具体实现在 ApplicationPackageManager中,查阅其它资料了解到这个方法应该是给系统级别的操作使用的,日常开发app应该基本用不到.

Android 4.4 , 5.0 , 6.0对权限的处理方法

终于讲到安卓各版本对申请权限的不同处理办法了.总的来说android对权限的控制是越来越强的.

在4.4系统上,只要开发者在 manifest 里面申请对应的权限,apk在安装到手机上后,都会弹出权限列表展示给用户该app将要获取到哪些权限,只要用户同意了安装这些权限就会自动被授予给app.

5.0系统对权限的处理与4.4基本没有差别,但一些第三方厂商的rom在应用管理中提供可关闭权限的开关,这样做就造成了很大的适配困扰,等下后面再讲.

6.0以上的系统在处理申请权限时,会弹出dialog来询问用户是否授予该权限,除了同意和拒绝dialog还会提供不再询问这个选项.

主动申请权限的最佳实践和碎片化处理

在 Android 6.0 以上主动申请权限时,谷歌的官方文档和视频给出了最佳实践 :

用户如果勾选了不再询问选项,则后续申请该权限时将不再弹出对话框,如果用户拒绝过该权限的申请但没有勾选不再询问,那么ActivityCompat.shouldShowRequestPermissionRationale()将返回true,开发者可在此时展示一个界面来介绍为何应用需要使用该权限,来争取用户授予权限

Paste_Image.png

上图是官方文档中对于权限申请的最佳实践代码.

12月13号:补充shouldShowRequestPermissionRationale()的原理

shouldShowRequest.png

可以看到 shouldShowRequestPermissionRationale 这个方法只有在API 23 上才会生效.

image.png

最终调用的函数的逻辑如下: 首先判断权限如果被授予了就会直接返回false,然后会判断该权限是否不含 SYSTEM,POLICY,USER 这三个标志位并且含有 USER_SET这个标志位.

FLAG.png

SYSTEM.png

再看这几个标志位的含义:大意就是权限的授予状态是否被FIXED了也就是锁死了,而锁死的原因可能是用户点选了不再询问, 也有可能是因为该app是系统app。

Paste_Image.png

处理的结果会在 onRequestPermissionResult() 中返回,开发者可在这个回调中获取每一项权限具体的申请情况,然后提示对应的信息给用户。

在Flyme5.1 上测试,发现targetsdk<23 使用 checkSelfPermission仍然失效,并且会有动态权限申请的弹框出现,这说明flyme在5.0上也做了动态权限的逻辑.

在Android 5.0上,上述的 CheckPermission(),requestPermission()方法统统不能使用,因为这些api都是在6.0之后才加入的,但偏偏某些第三方rom加入了权限的开关界面,这样一来,如果用户在安装apk后进入应用管理界面手动关闭了权限,那么必定会对app的正常逻辑造成影响,而此时又没有api可以主动获取权限的获取状况.

关于这点,我参阅了 腾讯bugly的一篇文章,这篇文章给出的解决方案是通过反射 AppOpsManager 这个类并调用 checkOp(int op, int uid,String packageName) 方法来检查是否获取了权限,之后再跳转到每个rom具体的应用管理开关界面来,然后主动提示用户打开开关.文章中提到可以通过 adb shell dumpsys activity activities 这个命令来获取各个row的权限开关界面,然后再通过显式intent的方法跳转到具体界面,但经过我的测试, checkOp(int op,int uid,String PackageName)方法无法保证100%能正常运行,这恐怕也是因为第三方rom对这个类的修改.

所以在 加入权限开关的5.0第三方rom系统,暂时找不到一个完美的解决方案,当然这也可能是因为我测试的范围不够大也不够精细,所以如果有哪位阅读了本文的开发者有完美的解决方案,请一定在我的文章下留言帮我解惑.

对于 signature 权限的一点想法

在介绍 protectionLevel 的时候,有这么一个级别 signature ,关于它的解释是:只有证书也就是keystore相同的应用才能被授予该项权限,而我们又知道 activity等组件是可以声明android:permission这项属性的,结合自定义权限使用就可以保证只有拥有了特定权限才能使用该组件.但我们知道 manifest中声明的自定义权限是可以被轻易的看到的,某些恶意应用和开发者在manifest中找到这些自定义权限后完全可以通过嗅探来攻击四大组件.

你可能会问只要把四大组件的 exported属性声明为false不就可以了么,但是这样一来,某些被应用信任的进程或插件运行的进程就无法访问到该组件了.

我想,解决的办法就是使用为自定义权限声明 signature_ 危险级别,这样一来只有拥有相同证书的apk跑起来的进程可以使用该权限,在很大程度上保证了app的安全.

不过这只是我结合官方文档的合理推论,并没有实际实践过,如果有哪位大神实践过,请一定帮我指正错误之处.