HarmonyOS Developer 开发指导
访问控制(权限)开发概述
ATM (AccessTokenManager) 是HarmonyOS上基于AccessToken构建的统一的应用权限管理能力。
默认情况下,应用只能访问有限的系统资源。但某些情况下,应用为了扩展功能的诉求,需要访问额外的系统或其他应用的数据(包括用户个人数据)、功能。系统或应用也必须以明确的方式对外提供接口来共享其数据或功能。HarmonyOS提供了一种访问控制机制来保证这些数据或功能不会被不当或恶意使用,即应用权限。
应用权限保护的对象可以分为数据和功能:
- 数据包含了个人数据(如照片、通讯录、日历、位置等)、设备数据(如设备标识、相机、麦克风等)、应用数据。
- 功能则包括了设备功能(如打电话、发短信、联网等)、应用功能(如弹出悬浮框、创建快捷方式等)等。
应用权限是程序访问操作某种对象的通行证。权限在应用层面要求有明确定义,应用权限使得系统可以规范各类应用程序的行为准则,实现用户隐私的保护机制。当应用访问操作目标对象时,目标对象会对应用进行权限检查,如果没有对应权限,则访问操作将被拒绝。
当前,ATM提供的应用权限校验功能是基于统一管理的TokenID(Token identity)。TokenID是每个应用的身份标识,ATM通过应用的TokenID来管理应用的权限。
权限使用的基本原则
在进行权限的申请和使用时,需要满足以下基本原则:
- 应用申请的权限,都必须有明确、合理的使用场景和功能说明,确保用户能够清晰明了地知道申请权限的目的、场景、用途;禁止诱导、误导用户授权;应用使用权限必须与申请所述一致。
- 应用权限申请遵循最小化原则,只申请业务功能所必要的权限,禁止申请不必要的权限。
- 应用在首次启动时,避免频繁弹窗申请多个权限;权限须在用户使用对应业务功能时动态申请。
- 用户拒绝授予某个权限时,与此权限无关的其他业务功能应能正常使用,不能影响应用的正常注册或登录。
- 业务功能所需要的权限被用户拒绝且禁止后不再提示,当用户主动触发使用此业务功能或为实现业务功能所必须时,应用程序可通过界面内文字引导,让用户主动到“系统设置”中授权。
- 当前不允许应用自行定义权限,应用申请的权限应该从已有的权限列表中选择。
权限的工作流程
权限申请使用的工作流程
应用在访问数据或者执行操作时,需要评估该行为是否需要应用具备相关的权限。如果确认需要目标权限,则需要在应用安装包中申请目标权限。
然后,需要判断目标权限是否属于用户授权类。如果是,应用需要使用动态授权弹框来提供用户授权界面,请求用户授权目标权限。
当用户授予应用所需权限后,应用可成功访问目标数据或执行目标操作。
应用使用权限的工作流程如图所示。
- 开发者可以参考已有的权限列表,判断应用能否申请目标权限。
权限校验的工作流程
应用在提供对外功能服务接口时,可以根据接口涉数据的敏感程度或所涉能力的安全威胁影响,在权限定义列表选择合适的权限保护当前接口,对访问者进行权限校验。
当且仅当访问者获取当前接口所需权限后,才能通过当前接口的权限校验,并正常使用当前应用提供的目标功能。
应用使用权限校验的工作流程如图所示。
- 根据应用当前提供的接口是否涉及敏感的数据或者功能,使用应用权限对当前接口进行访问控制。
- 应用可以在已有的权限列表选择适合的权限。比如应用提供的接口会涉及到麦克风使用的话,推荐使用麦克风相关的权限对接口进行保护。
- 应用可以使用权限校验接口对访问者进行鉴权,可参考权限校验说明。
权限等级说明
根据接口所涉数据的敏感程度或所涉能力的安全威胁影响,ATM模块定义了不同开放范围的权限等级来保护用户隐私。
应用APL等级说明
元能力权限等级APL(Ability Privilege Level)指的是应用的权限申请优先级的定义,不同APL等级的应用能够申请的权限等级不同。
应用的等级可以分为三个等级,分别是:
APL级别 | 说明 |
system_core等级 | 该等级的应用服务提供操作系统核心能力。 |
system_basic等级 | 该等级的应用服务提供系统基础服务。 |
normal等级 | 普通应用。 |
默认情况下,应用的APL等级都为normal等级。
权限等级说明
根据权限对于不同等级应用有不同的开放范围,权限类型对应分为以下三种,等级依次提高。
- normal权限
normal 权限允许应用访问超出默认规则外的普通系统资源。这些系统资源的开放(包括数据和功能)对用户隐私以及其他应用带来的风险很小。
该类型的权限仅向APL等级为normal及以上的应用开放。 - system_basic权限
system_basic权限允许应用访问操作系统基础服务相关的资源。这部分系统基础服务属于系统提供或者预置的基础功能,比如系统设置、身份认证等。这些系统资源的开放对用户隐私以及其他应用带来的风险较大。
该类型的权限仅向APL等级为system_basic及以上的应用开放。 - system_core权限
system_core权限涉及到开放操作系统核心资源的访问操作。这部分系统资源是系统最核心的底层服务,如果遭受破坏,操作系统将无法正常运行。
鉴于该类型权限对系统的影响程度非常大,目前暂不向任何三方应用开放。
权限类型说明
根据授权方式的不同,权限类型可分为system_grant(系统授权)和user_grant(用户授权)。
- system_grant
system_grant指的是系统授权类型,在该类型的权限许可下,应用被允许访问的数据不会涉及到用户或设备的敏感信息,应用被允许执行的操作不会对系统或者其他应用产生大的不利影响。
如果在应用中申请了system_grant权限,那么系统会在用户安装应用时,自动把相应权限授予给应用。应用需要在应用商店的详情页面,向用户展示所申请的system_grant权限列表。 - user_grant
user_grant指的是用户授权类型,在该类型的权限许可下,应用被允许访问的数据将会涉及到用户或设备的敏感信息,应用被允许执行的操作可能对系统或者其他应用产生严重的影响。
该类型权限不仅需要在安装包中申请权限,还需要在应用动态运行时,通过发送弹窗的方式请求用户授权。在用户手动允许授权后,应用才会真正获取相应权限,从而成功访问操作目标对象。
比如说,在权限定义列表中,麦克风和摄像头对应的权限都是属于用户授权权限,列表中给出了详细的权限使用理由。
应用需要在应用商店的详情页面,向用户展示所申请的user_grant权限列表。
不同权限类型的授权流程
如权限的工作流程所示,如果应用需要获取目标权限,那么需要先进行权限申请。
- 权限申请
开发者需要在配置文件中声明目标权限。 - 权限授权
- 如果目标权限是system_grant类型,开发者在进行权限申请后,系统会在安装应用时自动为其进行权限预授予,开发者不需要做其他操作即可使用权限。
- 如果目标权限是user_grant类型,开发者在进行权限申请后,在运行时触发动态弹窗,请求用户授权,具体操作见user_grant权限请求授权的步骤详解。
user_grant权限请求授权的步骤详解
在应用需要获取user_grant权限时,请完成以下步骤:
- 在配置文件中,声明应用需要请求的权限,详见访问控制开发指导。
- 将应用中需要申请权限的目标对象与对应目标权限进行关联,让用户明确地知道,哪些操作需要用户向应用授予指定的权限。
- 运行应用时,在用户触发访问操作目标对象时应该调用接口,精准触发动态授权弹框。该接口的内部会检查当前用户是否已经授权应用所需的权限,如果当前用户尚未授予应用所需的权限,该接口会拉起动态授权弹框,向用户请求授权。
- 检查用户的授权结果,确认用户已授权才可以进行下一步操作。
注意事项:
- 每次执行需要目标权限的操作时,应用都必须检查自己是否已经具有该权限。
- 如需检查用户是否已向您的应用授予特定权限,可以使用verifyAccessToken函数,此方法会返回PERMISSION_GRANTED或PERMISSION_DENIED。具体的示例代码可以查看访问控制开发指导。
- user_grant权限授权要基于用户可知可控的原则,需要应用在运行时主动调用系统动态申请权限的接口,系统弹框由用户授权,用户结合应用运行场景的上下文,识别出应用申请相应敏感权限的合理性,从而做出正确的选择。
- 即使用户向应用授予过请求的权限,应用在调用受此权限管控的接口前,也应该先检查自己有无此权限,而不能把之前授予的状态持久化,因为用户在动态授予后还可以通过设置取消应用的权限。
访问控制授权申请指导
场景介绍
以下示例代码基于此场景假设:应用因为应用核心功能诉求,需要申请权限"ohos.permission.PERMISSION1"和权限"ohos.permission.PERMISSION2"。
- 应用的APL等级为normal。
- 权限"ohos.permission.PERMISSION1"的权限等级为normal,权限类型为system_grant。
- 权限"ohos.permission.PERMISSION2"的权限等级为system_basic, 权限类型为user_grant。
注意
当前场景下,应用申请的权限包括了user_grant权限,对这部分user_grant权限,可以先通过权限校验,判断当前调用者是否具备相应权限。
当权限校验结果显示当前应用尚未被授权该权限时,再通过动态弹框授权方式给用户提供手动授权入口。
应用可申请的权限,可查询应用权限列表。
接口说明
以下仅列举本指导使用的接口,不同模型下使用的拉起权限弹窗的接口有差异,更多说明可以查阅完整示例。
FA模型
接口名 | 描述 |
requestPermissionsFromUser(permissions: Array<string>, requestCallback: AsyncCallback<PermissionRequestResult>) : void | 拉起弹窗请求用户授权。 详情可查阅API参考。 |
Stage模型
接口名 | 描述 |
requestPermissionsFromUser(context: Context, permissions: Array<Permissions>, requestCallback: AsyncCallback<PermissionRequestResult>) : void | 拉起弹窗请求用户授权。 详情可查阅API参考。 |
权限申请声明
应用需要在工程配置文件中,对需要的权限逐个声明,没有在配置文件中声明的权限,应用将无法获得授权。HarmonyOS提供了两种应用模型,分别为FA模型和Stage模型,更多信息可以参考应用模型解读。
不同的应用模型的应用包结构不同,所使用的配置文件不同,请开发者在申请权限时注意区分。
配置文件标签说明如下表。
标签 | 说明 |
name | 权限名称。 |
reason | 当申请的权限为user_grant权限时,此字段必填,描述申请权限的原因。 |
usedScene | 当申请的权限为user_grant权限时,此字段必填,描述权限使用的场景和时机。 |
ability | 标识需要使用到该权限的Ability,标签为数组形式。 适用模型: FA模型 |
abilities | 标识需要使用到该权限的Ability,标签为数组形式。 适用模型: Stage模型 |
when | 标识权限使用的时机,值为"inuse/always",表示为仅允许前台使用和前后台都可使用。 |
FA模型
使用FA模型的应用,需要在config.json文件中声明权限。
示例:
{
"module" : {
"reqPermissions":[
{
"name" : "ohos.permission.PERMISSION1",
"reason": "$string:reason",
"usedScene": {
"ability": [
"FormAbility"
],
"when":"inuse"
}
},
{
"name" : "ohos.permission.PERMISSION2",
"reason": "$string:reason",
"usedScene": {
"ability": [
"FormAbility"
],
"when":"always"
}
}
]
}
}
Stage模型
使用Stage模型的应用,需要在module.json5文件中声明权限。
示例:
{
"module" : {
"requestPermissions":[
{
"name" : "ohos.permission.PERMISSION1",
"reason": "$string:reason",
"usedScene": {
"abilities": [
"FormAbility"
],
"when":"inuse"
}
},
{
"name" : "ohos.permission.PERMISSION2",
"reason": "$string:reason",
"usedScene": {
"abilities": [
"FormAbility"
],
"when":"always"
}
}
]
}
}
申请授权user_grant权限
在前期的权限声明步骤后,在安装过程中系统会对system_grant类型的权限进行权限预授权,而user_grant类型权限则需要用户进行手动授权。
所以,应用在调用受"ohos.permission.PERMISSION2"权限保护的接口前,需要先校验应用是否已经获取该权限。
如果校验结果显示,应用已经获取了该权限,那么应用可以直接访问该目标接口,否则,应用需要通过动态弹框先申请用户授权,并根据授权结果进行相应处理,处理方式可参考访问控制开发概述。
注意
不能把之前授予的状态持久化,每次访问受目标权限保护的接口前,都应该调用requestPermissionsFromUser接口请求权限,因为用户在动态授予后可能通过设置取消应用的权限。
完整示例
请求用户授权权限的开发步骤为:
- 获取ability的上下文context。
- 调用requestPermissionsFromUser接口请求权限。运行过程中,该接口会根据应用是否已获得目标权限决定是否拉起动态弹框请求用户授权。
- 根据requestPermissionsFromUser接口返回值判断是否已获取目标权限。如果当前已经获取权限,则可以继续正常访问目标接口。
FA模型下的示例代码
import featureAbility from '@ohos.ability.featureAbility';
reqPermissions() {
let context = featureAbility.getContext();
let array:Array<string> = ["ohos.permission.PERMISSION2"];
//requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗
context.requestPermissionsFromUser(array, 1).then(function(data) {
console.log("data:" + JSON.stringify(data));
console.log("data permissions:" + JSON.stringify(data.permissions));
console.log("data result:" + JSON.stringify(data.authResults));
}, (err) => {
console.error('Failed to start ability', err.code);
});
}
说明
FA模型动态授权申请接口的使用详见API参考。
Stage模型下的示例代码
import abilityAccessCtrl from '@ohos.abilityAccessCtrl';
//ability的onWindowStageCreate生命周期
onWindowStageCreate() {
var context = this.context
var AtManager = abilityAccessCtrl.createAtManager();
//requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗
AtManager.requestPermissionsFromUser(context, ["ohos.permission.CAMERA"]).then((data) => {
console.log("data type:" + typeof(data));
console.log("data:" + data);
console.log("data permissions:" + data.permissions);
console.log("data result:" + data.authResults);
}).catch((err) => {
console.error('Failed to start ability', err.code);
})
}
说明
Stage模型动态授权申请接口的使用详见API参考。
访问控制权限校验指导
场景介绍
应用在提供对外功能服务接口时,可以根据接口涉数据的敏感程度或所涉能力的安全威胁影响,在系统定义的权限列表中权限定义列表选择合适的权限限制当前接口的开放范围,对接口访问者进行权限校验。
接口说明
以下仅列举本指导使用的接口,更多说明可以查阅API参考。
接口名 | 描述 |
checkAccessToken(tokenID: number, permissionName: Permissions): Promise<GrantStatus> | 校验指定的应用进程是否已被授权指定的权限。 |
完整示例
进行权限校验的开发步骤为:
- 获取调用者的身份标识:tokenId。
说明
获取访问者身份标识tokenId的方法 getCallingTokenId 可参考API参考。
- 待校验的权限名:ohos.permission.ACCELEROMETER。
- 使用checkAccessToken接口对当前调用者进行权限校验。
- 根据权限校验结果采取对应的措施。
import abilityAccessCtrl from '@ohos.abilityAccessCtrl'
import rpc from '@ohos.rpc'
class Stub extends rpc.RemoteObject {
onRemoteRequest(code, data, reply, option) {
let callerTokenId = rpc.IPCSkeleton.getCallingTokenId();
console.log("RpcServer: getCallingTokenId result: " + callerTokenId);
var atManager = abilityAccessCtrl.createAtManager();
try {
atManager.checkAccessToken(callerTokenId, "ohos.permission.ACCELEROMETER").then((data) => {
console.log(`checkAccessToken success, data->${JSON.stringify(data)}`);
}).catch((err) => {
console.log(`checkAccessToken fail, err->${JSON.stringify(err)}`);
});
} catch(err) {
console.log(`catch err->${JSON.stringify(err)}`);
}
return true;
}
}