HarmonyOS的访问控制权限

284 阅读7分钟

一.权限等级

根据接口所涉数据的敏感程度或所涉能力的安全威胁影响,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权限列表。

不同权限类型的授权流程

如果应用需要获取目标权限,那么需要先进行权限申请。

  • 权限申请 开发者需要在module.json5配置文件中声明目标权限。
  • 权限授权
    • 如果目标权限是system_grant类型,开发者在进行权限申请后,系统会在安装应用时自动为其进行权限预授予,开发者不需要做其他操作即可使用权限。 
    • 如果目标权限是user_grant类型,开发者在进行权限申请后,在运行时触发动态弹窗,请求用户授权。

user_grant权限请求授权的步骤详解

在应用需要获取user_grant权限时,请完成以下步骤:

  1. module.json5配置文件中,声明应用需要请求的权限。
  2. 将应用中需要申请权限的目标对象与对应目标权限进行关联,让用户明确地知道,哪些操作需要用户向应用授予指定的权限。
  3. 运行应用时,在用户触发访问操作目标对象时应该调用接口,精准触发动态授权弹框。该接口的内部会检查当前用户是否已经授权应用所需的权限,如果当前用户尚未授予应用所需的权限,该接口会拉起动态授权弹框,向用户请求授权。
  4. 检查用户的授权结果,确认用户已授权才可以进行下一步操作。

注意事项:

  • 每次执行需要目标权限的操作时,应用都必须检查自己是否已经具有该权限。 
  • 如需检查用户是否已向您的应用授予特定权限,可以使用函数 checkAccessToken ,此方法会返回 PERMISSION_GRANTED(已授权)或PERMISSION_DENIED(未授权)。 
  • user_grant权限授权要基于用户可知可控的原则,需要应用在运行时主动调用系统动态申请权限的接口,系统弹框由用户授权,用户结合应用运行场景的上下文,识别出应用申请相应敏感权限的合理性,从而做出正确的选择。 
  • 即使用户向应用授予过请求的权限,应用在调用受此权限管控的接口前,也应该先检查自己有无此权限,而不能把之前授予的状态持久化,因为用户在动态授予后还可以通过设置取消应用的权限。

三.module.json5配置文件权限声明

配置文件标签说明如下表所示。

标签是否必填说明
name权限名称。
reason描述申请权限的原因。> 说明:当申请的权限为user_grant权限时,此字段必填。
usedScene描述权限使用的场景和时机。> 说明:当申请的权限为user_grant权限时,此字段必填。
abilities标识需要使用到该权限的Ability,标签为数组形式
when标识权限使用的时机,值为inuse/always。- inuse:表示为仅允许前台使用。- always:表示前后台都可使用。
{
  "module": {
    "requestPermissions":[
      {
        "name" : "ohos.permission.READ_CALENDAR",// 权限名称
        "reason": "$string:ControlPermissions_rili",//申请权限的理由
        // 使用场景和时机,当申请的权限为user_grant权限时,此字段必填
        "usedScene": {
          // 需要使用到该权限的ability
          "abilities": [
            "EntryAbility"
          ],
          /*
          * 标识权限使用的时机,值为inuse/always。
          * - inuse:表示为仅允许前台使用。
          * - always:表示前后台都可使用
          */
          "when":"inuse"
        }
      }
    ]
  }

四.向用户申请权限

APP启动时在Ability中申请

1.导入访问控制管理模块,并定义好权限数组

import abilityAccessCtrl, { Permissions } from '@ohos.abilityAccessCtrl';
const permissions: Array<Permissions> = [
  'ohos.permission.READ_CALENDAR',//日历
  'ohos.permission.MICROPHONE',//麦克风
  'ohos.permission.LOCATION',//位置
  'ohos.permission.CAMERA'//相机
];

2.自定义一个权限申请方法

applyForPermission() {
  let context = this.context;
  let atManager = abilityAccessCtrl.createAtManager();
  // requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗
  atManager.requestPermissionsFromUser(context, permissions).then((data) => {
    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) => {
    console.error(`requestPermissionsFromUser failed, code is ${err.code}, message is ${err.message}`);
  })
}

3.在onWindowStageCreate中调用

onWindowStageCreate(windowStage: window.WindowStage) {
  this.applyForPermission()
}

动态向用户申请

1.导入需要模块并定义权限数组

// 访问控制管理模块
import abilityAccessCtrl, { Permissions } from '@ohos.abilityAccessCtrl';
// 公共模块
import common from '@ohos.app.ability.common';
// 应用信息查询
import bundleManager from '@ohos.bundle.bundleManager';
const permissions: Array<Permissions> = [
  'ohos.permission.READ_CALENDAR',//日历
  'ohos.permission.MICROPHONE',//麦克风
  'ohos.permission.LOCATION',//位置
  'ohos.permission.CAMERA'//相机
];

2.在需要开启权限时动态申请

Button('申请权限').onClick((event: ClickEvent) => {
  this.applyForPermission()
})

// 申请权限
applyForPermission(): void {
  // 获取UIAbility的上下文环境
  let context = getContext(this) as common.UIAbilityContext;
  // 获取访问控制模块对象
  let atManager = abilityAccessCtrl.createAtManager();
  // requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗
  atManager.requestPermissionsFromUser(context, permissions).then((data) => {
    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) => {
    console.error(`requestPermissionsFromUser failed, code is ${err.code}, message is ${err.message}`);
  })
}

完整的权限申请流程

1.获取授权状态

// 获取授权状态
async function checkAccessToken(permission: Permissions): Promise<abilityAccessCtrl.GrantStatus> {
  let atManager = abilityAccessCtrl.createAtManager();
  // 授权状态的枚举
  /**
   * PERMISSION_DENIED -1 表示未授权。
   * PERMISSION_GRANTED 0 表示已授权。
   * */
  let grantStatus: abilityAccessCtrl.GrantStatus;

  // 获取应用程序的accessTokenID
  let tokenId: number;
  try {
    /**
     * 以异步方法根据给定的bundleFlags获取当前应用的BundleInfo,使用Promise形式返回结果。
     * GET_BUNDLE_INFO_WITH_APPLICATION:用于获取包含applicationInfo的bundleInfo
     * */
    let bundleInfo: bundleManager.BundleInfo = await bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION);
    // 获取有关应用程序的配置信息
    let appInfo: bundleManager.ApplicationInfo = bundleInfo.appInfo;
    tokenId = appInfo.accessTokenId;
  } catch (err) {
    console.error(`getBundleInfoForSelf failed, code is ${err.code}, message is ${err.message}`);
  }

  // 校验应用是否被授予权限
  try {
    // 校验应用是否授予权限,使用Promise方式异步返回结果
    grantStatus = await atManager.checkAccessToken(tokenId, permission);
  } catch (err) {
    console.error(`checkAccessToken failed, code is ${err.code}, message is ${err.message}`);
  }

  return grantStatus;
}

2.判断是否已授权

// 判断是否已授权
async function checkPermissions(permission: Permissions): Promise<void> {
  let grantStatus: abilityAccessCtrl.GrantStatus = await checkAccessToken(permission);
  if (grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
    xzqAlert(true)
  } else {
    xzqAlert(false)
  }
}

3.根据授权状态进行弹窗提示

// 弹窗提示
function xzqAlert(per:boolean) {
  let msg = per ? '已授权' : '未授权'
  AlertDialog.show(
    {
      title: '提示',
      message: msg,
      autoCancel: true,
      alignment: DialogAlignment.Bottom,
      offset: { dx: 0, dy: -20 },
      gridCount: 3,
      confirm: {
        value: '确定',
        action: () => {
          if (!per) {
            openPermissionsInSystemSettings()
          }
        }
      }
    }
  )
}

4.未授权则跳到授权开启页

// 跳转到对应的权限开启页
function openPermissionsInSystemSettings(): void {
  let context = getContext(this) as common.UIAbilityContext;
  let wantInfo = {
    action: 'action.settings.app.info',
    parameters: {
      settingsParamBundleName: 'com.example.controlpermissions' // 打开指定应用的详情页面
    }
  }
  context.startAbility(wantInfo).then(() => {

  }).catch((err) => {

  })
}

五.权限列表

ohos.permission.READ_CALENDAR

允许应用读取日历信息。

权限级别:normal

授权方式:user_grant

ACL使能:TRUE

ohos.permission.MICROPHONE

允许应用使用麦克风。

权限级别:normal

授权方式:user_grant

ACL使能:TRUE

ohos.permission.LOCATION

允许应用获取设备位置信息。

权限级别:normal

授权方式:user_grant

ACL使能:TRUE

ohos.permission.CAMERA

允许应用使用相机拍摄照片和录制视频。

权限级别:normal

授权方式:user_grant

ACL使能:TRUE