1. Android权限管理
原作者: xiaoguang.dong
1.1. 基本概念
1.1.1. AndroidManifest权限相关标签
1.1.1.1. permission 标签
permission
标签用于声明一个权限android:name
指定权限名称android:protectionLevel
指定权限的保护级别android:protectionLevel
指定权限的保护级别
1.1.1.2. uses-permission标签
<uses-permission>
用于申请特定权限android:name
指定申请的权限名称android:maxSdkVersion
用于指定可被授权的最高的API level
在4.4平台(API level 19)之前,使用某个接口依赖于权限A;但是在4.4平台上A权限已经被废弃或者该接口已经不依赖于A权限,那么可以做如下配置:
<uses-permission android:name=“A” android:maxSdkVersion=“18”/>
那么在4.4平台之后,系统将不会授予应用A权限
1.1.1.3. permission-group标签
<permission-group>
用于标记权限的逻辑分组。android:name
用于指定权限组的名称。android:label
用于指定权限组的标签。android:descriptiopn
用于指定权限组的描述信息。android:icon
用于指定权限组的图标。
如果权限有
Permission-group
属性,用户在Setting
权限界面看到的就是permission-group
的label
和description
,否则使用permission
本身的label
和description
。
1.1.1.4. permission-tree标签
1 | <permission-tree android:icon="drawable resource" |
注意这个元素自身不声明一个权限,只是一个名字空间,更多的权限可以被放置在它里面。
<permission-tree>
用于定义一组权限的命名空间。作用是可以使程序在运行时动态声明权限。android:name
用于指定权限树根节点的名称。android:label
组的用户可读名称, 标签可以直接设置为一个原始字符串, 使它可以像用户界面中的其它字符串那样被本地化。
如果某个应用使用了
<permission-tree>
标签,可以通过调用PackageManager.addPermission()
方法来动态声明新权限。假如根节点的名称是com.sprd.core
,则可以声明如下权限:com.sprd.core.CONTACTS
com.sprd.core.MMS
…
1.1.2. 权限控制
1.1.2.1. 四大组件权限检查
android:permission
适用于activity
/service
/provider
/receiver
标签。
以
activity
为例,假如服务端activity
配置了android:permission
属性,客户端在调用startActivity()
或startActivityForResult()
启动服务端activity
时,如果没有被授予指定权限,其Intent
将不会传递给服务端Activity
。如果未设置该属性,则对Activity
应用<application>
元素的permission
属性设置的权限。 如果这两个属性均未设置,则Activity
不受权限保护。
1.1.2.2. shareUserId 属性
android:SharedUserId
指定与其它应用共享一个UID
,只有两个使用相同证书签名(并且请求相同的sharedUserId
)的应用才被分配同一UID
。通过共享UID
,不同的APK
之间可以实现数据共享。
PackageManager
初始化时默认提供6
个系统级sharedUserId
,均使用platform
证书验证。
1 | android.uid.system |
1.1.2.3. 权限级别/ProtectionLevel
1.1.2.3.1. normal
低风险权限,只要申请了就可以使用(在AndroidManifest.xml中添加uses-permission标签),安装时不需要用户确认
1.1.2.3.2. Dangerous
高风险权限,需要用户的确认才可被授权
1.1.2.3.3. Signature
当申请权限的APK与声明此权限的APK的使用的签名证书相同时,才能被授权
1.1.2.3.4. SignatureOrSystem
- 当申请权限的APK与声明此权限的APK的使用的签名证书相同时,才能被授权。
- 申请权限的应用为系统应用
1.1.2.4. Signature vs SignatureOrSystem
- 针对
ProtectionLevel
中signatureOrSystem
的应用,有如下应用场景:如果有多个不同的厂商同时在system/app
下预制了多个不同的APK
,每个APK
签名使用的证书是不同的。但是这些APK
之间彼此还希望访问对方的一些数据。此时就可以使用signatureOrSystem
权限来保护应用数据。 - 与之而来的问题在于,所有
/system/app
下的应用都可以访问用signatureOrSystem
访问的数据,虽然可以用getCallingUid
,getCallingPackage
来确认访问者的身份。但是这里是存在隐患的。 - 添加
priva-app
目录解决了这个问题。在4.4
以后SignatureOrSystem
级别的权限只有具有相同签名,或者预制在priv-app
下的应用才被授权。 - 默认情况下预制在
priv-app
下的应用必须Google
原生应用或是OEM
厂商确保安全的第三方应用。
1.1.2.5. 定义权限作用?怎样通过权限实现应用数据和资源的保护?
- 程序的基础是
代码
和数据
,所以定义权限的两个目的就是控制代码执行流程
和保护数据/文件
。 - 针对
AndroidManifest
中定义的各种权限,其权限的授权状态都会存储在PackageManagerService
中,系统/APP
可以通过PackageManager
相关接口来检查调用端进程UID
是否具有特定权限,进而决定是否允许其进一步执行代码或者访问数据/文件。可以将PackageManager
看做一个权限管理的中转站。
1.1.2.6. Native层的权限控制
Native层的权限控制建立在Linux已有的uid/gid/gids基础上的。
UID
:Android
每安装一个应用程序,就会为它分配一个uid
。其中普通Android
应用程序的uid
是从10000
开始分配 (Process.FIRST_APPLICATION_UID
),10000
以下是系统应用预留的uid
GID
: 对于普通应用程序来说,gid
等于uid
。- 由于每个应用程序的
uid
和gid
都不相同, 因此不管是native
层还是java
层都能够达到保护私有数据的作用。 GIDS
: 在Apk
安装过程中生成,与申请的具体权限相关。 如果APK某个申请的permission
被granted
,而且这个权限在/system/etc/permisions/*xml
中有对应的gid
, 那么 这个 Apk对应的进程gids
中将 包含xml
中配置的gid
。
Eg
: 在Android5.1
平台上,应用申请了如下权限:<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
,同时在frameworks\base\data\etc\Platform.xml
中由如下配置:
1 | <permission name="android.permission.WRITE_EXTERNAL_STORAGE" > |
对于申请WRITE_EXTERNAL_STORAGE
特权的应用,进程的gids
就包含了sdcard_rw
,就可以对sd
卡中的文件进行操作。
1.2. 运行时权限
normal
/signature
/signatureOrSystem
会在App
安装时判断是否授权,可以看做是安装时权限。- 从
AndroidM
开始,Google
提出了运行时权限(runtime permission
)的概念。针对protectionLevle
为dangerous
的权限,在安装时默认不授权,而是在运行时由用户动态授权。 - 对于运行时权限,要求App在代码中做一定的适配,在运行时动态向用户请求相关权限。
- 在多用户模式下,所有用户共享安装时权限,独占运行时权限。
Android
定义的Runtime权限一览共9组25个:
1.2.1. 注意事项
- 如果应用开发时使用的
targetSDK
低于23
,在安装到M
和以上平台时,考虑到不支持动态权限相关的接口。系统会默认授予所有权限。- 同一组的任何一个权限被授权了,其他权限也自动被授权。不需要重复申请。
- 只申请需要的权限,需要的时候再申请权限;每次权限申请,都会弹出权限申请窗口,打断用户的操作。建议只申请你需要的权限。
Setting
中动态关闭权限会导致进程被Kill
。- 权限请求需要弹出
Dialog
,因此请求代码要添加到Activity
或Fragment
中- 尽可能避免在
Service
中申请权限。如果确实需要,要做一个work around
。比如弹出一个Notification
,提醒用户缺少相关权限。当用户点击Notification
在弹出权限申请框,继续权限申请流程。
1.3. 默认授权机制
- 为了确保
Android
原生应用,或者GMS
包中的应用在开机后具有必需的权限。比如CameraApp
默认授予Camera Group
相关权限,Contacts
默认授予Contacts Group
相关权限…. PackageManager
默认在首次开机/恢复出厂设置后,对一部分系统应用默认授予一些必需的权限。
1.3.1. 首次开机时默认授权的应用
应用使用requestPermissions
接口申请运行时权限,首次开机时,满足下列条件时,会对相应的权限进行授予:
UID
<10000
的应用- 同时满足如下条件的应用:
- 预制在
/system/priv-app
下。 Application
标签中配置了android:persistence=“true”
。- 使用
Platform
证书签名。
- 预制在
- 通过
Intent
匹配查询出的默认应用,为其授予合适分组的权限。- 以
Music
为例,代码中会自动匹配如下Intent
对应的component
:
- 以
1 | Intent musicIntent = new Intent(Intent.ACTION_VIEW); |
默认情况下,
6.0
和7.0
下匹配结果是不一致的。如果有多个同类型的应用,在6.0
上会根据返回结果取第一个元素进行授权;在7.0
上则会进行一系列比较,选出优先级最高的那个进行授权。如果前两个元素的优先级相同,则不会对任何一个匹配到的元素授权。
- 对于首次开机授权的应用,有一部分权限是系统固定的,用户无法修改。在
Setting->APP
中也看不到对应的权限状态。 - 修改默认授权的逻辑会导致
GTS CASE
失败。
1.3.2. 查看Package当前的权限状态
通过adb shell dumpsys package
可以查看某个Package当前的权限状态: