鸿蒙因为加强了系统安全,有了一套比较系统化的设计,所以在应用权限申请上涉及了挺多概念,看官方文档内容多容易迷糊。于是本文把主线流程精简出来,补充上一些支撑技术原理,也许能帮助提高些理解效率,常用的部分总体来说还是比较简单的,看完这篇应该就会用了。
ohos权限控制的整体框架ATM
说到OpenHarmonyOS的应用访问控制就必须先说ATM。这个ATM可不是提款机,根据官方说法,ATM(Access Token Manager)是OpenHarmonyOS上基于AccessToken构建的统一的应用权限管理能力。ATM通过对应用进行分级,执行最小授权原则,避免了权限滥用问题;同时支持跨平台执行权限访问控制功能,以便满足分布式应用场景。
应用的Accesstoken信息主要包括应用身份标识APPID、用户ID,应用分身索引、应用APL(Ability Privilege Level)等级、应用权限信息等。每个应用的Accestoken信息由一个32bits的设备内唯一标识符TokenID(Token identity)来标识。对于运行在设备上的应用, ATM为每个应用分配唯一的标识(TokenID),在资源使用时通过TokenID作为唯一身份标识映射获取对应应用的权限和授权状态信息,并依此进行鉴权,以管控应用的资源访问行为。拿腾讯视频来打比方,TokenID就类似腾讯视频的用户ID。
ATM模块主要提供如下功能:
- 提供基于TokenID的应用权限校验机制,应用访问敏感数据或者API时可以检查是否有对应的权限。
- 提供基于TokenID的Accestoken信息查询,应用可以根据TokenID查询自身的APL等级等信息。
应用在安装后首次启动时,如果遇到需要访问特定资源时,如:相册、摄像头等,会需要通过TokenID申请授权才能使用。
ATM部件的架构图如下所示:
ATM总体管理服务作为ATM 服务的总体入口,它可以将业务请求分发到下级的三个模块:TokenID管理、权限管理和APL管理。
TokenID管理模块:在TokenID与AT信息一一对应的基础上,TokenID管理提供了TokenID及其对应AT信息的初始化创建、查询、更新以及删除等服务。当目标应用被拉起时,ATM会为其分配唯一身份标识TokenID,并保存应用的初始化AT信息。。
● TokenID是每个应用的身份标识(可以理解为应用的身份证)。
● AT信息包括应用身份标识APP ID、子用户ID、应用分身索引信息、应用APL、应用权限授权状态等信息(可以理解为应用的身份证信息)。
权限管理模块:主要提供应用权限定义信息、应用权限授权状态信息的处理服务。权限管理模块在TokenID管理模块的基础上,向业务提供应用的权限信息查询、授权、鉴权等服务,管理应用权限的使用记录,构筑ATM的应用权限访问控制功能。
APL管理模块:APL管理模块基于唯一身份标识TokenID,提供应用的权限申请合法性校验功能,规范化权限申请范围,进行权限最小化管理。当前,应用的权限申请规则是基于洋葱式访问控制模型的,下面请跟随小编继续了解洋葱式访问控制模型。
对于应用来说,在ATM框架中主要是3个相关场景:
- 在应用安装时,需要调用AllocHapToken创建获取该应用的TokenID。
- 在应用运行过程中,需要进行鉴权等操作时,可调用VerifyAccessToken、GetReqPermissions等函数查询校验应用权限、APL等信息。
- 在应用卸载时,需要调用DeleteToken函数删除系统中管理的对应Accesstoken信息。
对于普通的应用开发来说,主要使用第2项里的接口,第1项和第3项由系统应用管理自动完成。而ATM提供的其它应用层功能,则根据需要视情况而用。
ohos的权限
OpenHarmonyOS的访问控制中,一方是访问者,即TokenID,另一方是被访问者,即权限。访问控制本质就是对这两者之间的访问关系进行管理。ohos将系统的各类可访问资源进行权限定义,以API10为例,提供了229项应用权限 。这个还是拿腾讯视频来打比方的话,权限就是视频资源。权限的定义示例如下图:
权限级别
权限级别即元能力权限等级APL (Ability Privilege Level),指的是应用的权限申请优先级的定义,不同APL等级的应用能够申请的权限等级不同。大白话就是应用也是本等级的,高等级的就是VVIP,默认就可以访问更核心的资源。应用的等级可以分为三个等级,如下表:
APL级别 | 说明 | 类比腾讯视频 |
---|---|---|
system_core等级 | 该等级的应用服务提供操作系统核心能力。 | 超级影视SVIP |
system_basic等级 | 该等级的应用服务提供系统基础服务。 | 腾讯视频VIP |
normal等级 | 普通应用。 | 普通会员 |
上面说的三个等级也正是基于洋葱式访问控制模型的设计,洋葱式访问控制模型分为三层,从里往外看:最里层是操作系统核心层,应用的APL=system_core。中间层是系统增强服务层,应用的APL=system_basic。最外层则是普通应用程序层,应用的APL=system_normal。
不同APL等级的应用能申请的权限范围也是不同的,下面一起看一下APL等级的划分规则。
A. 操作系统核心能力APL=“system_core”
操作系统核心能力是系统最核心的底层服务,它需要拥有所有权限以便实现对系统的管理。
操作系统核心能力包括:AMS(Ability Manager Service,能力管理系统)、BMS(Bundle Manager Service,包管理系统)、DMS(Distributed Manager Service,分布式消息系统)、软总线等。
操作系统核心能力的APL=“system_core”。这类应用可申请访问操作系统核心资源的权限,对系统的影响程度非常大,目前只对系统服务开放。
B. 系统基础服务APL=“system_basic”
在操作系统核心能力基础上,为操作系统提供基础服务的应用就叫系统基础服务,系统基础服务包括:
➢ 最小集基础应用,提供用户进行设备操作时所必需的最小集基础应用。如系统启动、系统设置、身份认证、系统调度和管理等。
➢ 智慧化服务,提供智慧化基础服务。如AR、VR、AI引擎的服务。
➢ 系统调度和管理应用,提供系统最基本的性能、功耗、后台应用的管理功能。
系统基础服务的APL=“system_basic”,这类应用可申请访问操作系统基础服务相关资源的权限。
C. 普通应用APL=“normal”
普通应用包括三方应用和不在系统基础服务范围内的预置应用。三方应用包括社交类、资讯类、视频播放类、游戏类等应用。预置应用包括时钟、天气等应用。
普通应用的APL=“normal”。这类应用是操作系统非必要软件,通过开放接口即可实现此类应用的功能。
注:若应用想要提升自身的APL等级,需要通过应用市场的审核。
洋葱式访问控制模型通过对应用实行严格的等级制度管控,根据不同的等级制定不同的安全和访问控制策略,实现了权限范围可控目标。同时为应用在跨设备运行时提供统一的隐私保护机制。
默认情况下,应用的APL等级都为normal等级。如果应用需要将自身的APL等级声明为system_basic及以上的APL等级,在开发应用安装包时,要修改应用的Profile文件。不过直接修改应用Profile文件的方式,仅用于应用/服务调试阶段,不可用于发布上架应用市场。如果需要开发商用版本的应用,要在对应的应用市场进行发布证书和Profile文件的申请。
授权方式
权限类型可分为system_grant(系统授权)和user_grant(用户授权)。
-
system_grant
system_grant指的是系统授权类型,在该类型的权限许可下,应用被允许访问的数据不会涉及到用户或设备的敏感信息,应用被允许执行的操作不会对系统或者其他应用产生大的不利影响。
如果在应用中申请了system_grant权限,那么系统会在用户安装应用时,自动把相应权限授予给应用。应用需要在应用商店的详情页面,向用户展示所申请的system_grant权限列表。相当于安装前告知,如果不允许则不要安装,安装即允许。
-
user_grant
user_grant指的是由用户动态授权,在该类型的权限许可下,应用被允许访问的数据将会涉及到用户或设备的敏感信息,应用被允许执行的操作可能对系统或者其他应用产生严重的影响。
该类型权限不仅需要在安装包中申请权限,还需要在应用动态运行时,通过发送弹窗的方式请求用户授权。在用户手动允许授权后,应用才会真正获取相应权限,从而成功访问操作目标对象。
比如说,在权限定义列表中,麦克风和摄像头对应的权限都是属于用户授权权限,列表中给出了详细的权限使用理由。
应用需要在应用商店的详情页面,向用户展示所申请的user_grant权限列表。
如果应用需要获取目标权限,那么需要先进行权限申请。申请步骤如下:
-
权限声明
开发者需要在配置文件中声明目标权限。
-
权限授权
- 如果目标权限是system_grant类型,开发者在进行权限申请后,系统会在安装应用时自动为其进行权限预授予,开发者不需要做其他操作即可使用权限。
- 如果目标权限是user_grant类型,开发者在进行权限申请后,在运行时触发动态弹窗,请求用户授权。
ACL使能
ACL即访问控制列表(Access Control List),提供了解决低等级应用访问高等级权限问题的特殊渠道。类似于腾讯视频里的影片单独付费功能,即虽然你不是VIP,但如果你为这部影片单独支持10元钱,也是可以看滴。
如前面APL定义的,权限和APL有一一对应关系。原则上,低APL等级的应用默认无法使用更高等级的权限。这时候如果需要使用更高的权限,就需要看这个权限是否支持ACL使能。即通过特殊的声明,获取访问该权限的能力。
ACL使能有两种使用方法:
一种用于应用或服务开发调试阶段使用。直接修改SDK目录里的OpenHarmony SDK目录/Toolchains/{Version}/lib>UnsgnedReleasedProfileTemplate.json
文件。这里也有两种方式修改。一种是直接修改apl
配置项等级,把应用定义为更高权限级别的应用,就可以直接获取高权限的资源。第二种是修改文件中的acls
配置,把对应的权限名称填充到allowed-acls
数组里,通过这个声明,就可以获取访问资格。
另一种用于商用版本应用,需要在应用市场进行发布证书和Profile文件的申请。
起始版本
这个很好理解,表示从哪个API版本开始提供。比如:10则就是从支持API10的系统开始,都可以使用这个权限定义。
实例
上面的机制在实际应用开发中可以串成一个检查流程,重新整理了一个流程图如下:
拿腾讯视频的例子帮助理解就很简单:首先看你要访问什么资源,如果是普通视频,那就放广告后直接放行。如果是会员才能看的电影,那就要先看看你是什么身份的用户(normal、system_basic、system_core?)。如果你是超级VIP那就体贴的加上一段VIP专属的广告后才放行,如果你是普通会员就只让你看5分钟,当然广告还是不能忘了。如果你不想充会员,又想看这电影,也还是有办法的。如果这电影支持单独付费(ACL使能),那就看你付不付(配置允许ACL项),付了也放行。不付的话,哼哼……。当然重要的事情最终都需要用户确认服务协议 (user_grant),要尊重用户。有的小变化不需要麻烦用户,自己处理下就行(system_grant)。
实操需求:假如某即将改变世界的应用要获取设备的udid信息和日历信息,经过在权限列表中查询,需要申请如下两个权限。
- 日历读权限
- UDID访问权限
根据上面的流程可知,我们在应用开发中要做这样几件事:
1.声明应用所需权限
日历的读权限普通会员即可申请,但要用户授权,那么声明权限只需要在当前模块下的module.json5文件中加上配置项即可。
UDID信息权限需要system_basic的APL级别才能访问,我们的应用通常只是normal级别,所以需要看是否有ACL使能,由权限定义可知这个权限支持ACL使能,所以可以修改 OpenHarmony SDK目录/Toolchains/{Version}/lib>UnsgnedReleasedProfileTemplate.json
申请权限。
在module.json5文件的abilities元素同级增加requestPermissions
配置,对于需要用户在前台使用的权限需要在abilities
里配置上对应的ability
名称:
"requestPermissions": [
{
"name": "ohos.permission.sec.ACCESS_UDID",
"reason": "$string:reason",
"usedScene": {
"abilities": [
"EntryAbility"
],
"when": "always"
}
},
{
"name": "ohos.permission.READ_CALENDAR",
"reason": "$string:reason",
"usedScene": {
"abilities": [
"EntryAbility"
],
"when": "inuse"
}
}
]
应用需要在项目的module.json5配置文件中逐个声明所需的权限,否则应用将无法获取授权。不管是什么授权方式,system_grant(系统授权)还是user_grant(用户授权)都需要在配置文件中声明。
在配置文件的requestPermissions属性数组中配置所需的权限,包含3个属性:name、reason和usedScene。
其中:
- name 指定权限名称,是必填项。
- reason 描述申请权限的原因,可选项。
- usedScene 描述权限使用的场景和时机,可选项。
- abilities:标识需要使用到该权限的Ability,标签为数组形式。可选项。
- when:标识权限使用的时机,值为inuse/always。inuse:表示为仅允许前台使用;always:表示前后台都可使用。
在UnsgnedReleasedProfileTemplate.json中的acls
增加以下内容:
"acls":{
"allowed-acls":[
"ohos.permission.sec.ACCESS_UDID"
]
}
当然,你也可以直接提高应用APL等级,就不用一个个改acls了:
"apl":"system_basic",
2.申请用户动态授权
对于日历信息访问权限,由于它的授权方式是user_grant
,所以必须以动态弹框的方式向用户申请授权。
在进行权限申请之前,需要先检查当前应用程序是否已经被授予了权限。可以调用checkAccessToken()方法。如果已经授权,则可以直接访问目标操作,否则需要向用户申请授权。这部分可以直接使用openharmonyos在线文档的样例代码。
import bundleManager from '@ohos.bundle.bundleManager';
import abilityAccessCtrl, { Permissions } from '@ohos.abilityAccessCtrl';
import { BusinessError } from '@ohos.base';
async function checkAccessToken(permission: Permissions): Promise<abilityAccessCtrl.GrantStatus> {
let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
let grantStatus: abilityAccessCtrl.GrantStatus = abilityAccessCtrl.GrantStatus.PERMISSION_DENIED;
// 获取应用程序的accessTokenID
let tokenId: number = 0;
try {
let bundleInfo: bundleManager.BundleInfo = await bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION);
let appInfo: bundleManager.ApplicationInfo = bundleInfo.appInfo;
tokenId = appInfo.accessTokenId;
} catch (error) {
let err: BusinessError = error as BusinessError;
console.error(`Failed to get bundle info for self. Code is ${err.code}, message is ${err.message}`);
}
// 校验应用是否被授予权限
try {
grantStatus = await atManager.checkAccessToken(tokenId, permission);
} catch (error) {
let err: BusinessError = error as BusinessError;
console.error(`Failed to check access token. Code is ${err.code}, message is ${err.message}`);
}
return grantStatus;
}
async function checkPermissions(): Promise<void> {
const permissions: Array<Permissions> = ['ohos.permission.READ_CALENDAR'];
let grantStatus: abilityAccessCtrl.GrantStatus = await checkAccessToken(permissions[0]);
if (grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
// 已经授权,可以继续访问目标操作
} else {
// 申请日历权限
}
}
接上述代码,如果没授权,则需要动态向用户申请权限。动态向用户申请权限是指在应用程序运行时向用户请求授权的过程。可以通过调用requestPermissionsFromUser()方法来实现。该方法接收一个权限列表参数,例如位置、日历、相机、麦克风等。用户可以选择授予权限或者拒绝授权。
可以在UIAbility的onWindowStageCreate()
回调中调用requestPermissionsFromUser()方法来动态申请权限,也可以根据业务需要在UI中向用户申请授权。
这里以我们常用的UI为例,也可以直接查看在线文档:
import abilityAccessCtrl, { Context, PermissionRequestResult, Permissions } from '@ohos.abilityAccessCtrl';
import { BusinessError } from '@ohos.base';
const permissions: Array<Permissions> = ['ohos.permission.READ_CALENDAR'];
@Entry
@Component
struct Index {
reqPermissionsFromUser(permissions: Array<Permissions>): void {
let context: Context = getContext(this) as common.UIAbilityContext;
let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
// requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗
atManager.requestPermissionsFromUser(context, permissions).then((data: PermissionRequestResult) => {
let grantStatus: Array<number> = data.authResults;
let length: number = grantStatus.length;
for (let i = 0; i < length; i++) {
if (grantStatus[i] === 0) {
// 用户授权,可以继续访问目标操作
} else {
// 用户拒绝授权,提示用户必须授权才能访问当前页面的功能,并引导用户到系统设置中打开相应的权限
return;
}
}
// 授权成功
}).catch((err: BusinessError) => {
console.error(`Failed to request permissions from user. Code is ${err.code}, message is ${err.message}`);
})
}
// 页面展示
build() {
// ...
}
}
完成以上配置声明和动态授权两部分内容,则两个权限都可以使用了,不需要user_grant的权限只需要第1步声明权限就可以了。
参考资料:
访问控制(权限)开发概述 (openharmony.cn)
访问控制授权申请指导 (openharmony.cn)
应用权限列表 (openharmony.cn)
一文带你看懂ATM的应用权限访问控制能力_华为_科技观察-华为云开发者联盟 (csdn.net)
评论区