✨家人们记得点个账号关注,会持续发布大前端领域技术文章💕 🍃
权限管控是鸿蒙应用开发的基础安全能力,用于保护用户隐私数据(如照片、通讯录)和系统资源(如相机、麦克风)。鸿蒙采用 “数据为中心” 的安全理念,通过分级授权、动态申请等机制,将隐私控制权交还给用户。本章将从权限基础、申请流程、封装实战、面试考点四个维度,带你彻底掌握鸿蒙权限管理。
一、概述
系统提供了一种允许应用访问系统资源(如:通讯录等)和系统能力(如:访问摄像头、麦克风等)的通用权限访问方式,来保护系统数据(包括用户个人数据)或功能,避免它们被不当或恶意使用。
应用权限保护的对象可以分为数据和功能:
- 数据包括个人数据(如照片 优化了、通讯录、日历、位置等)、设备数据(如设备标识、相机、麦克风等)。
- 功能包括设备功能(如访问摄像头/麦克风、打电话、联网等)、应用功能(如弹出悬浮窗、创建快捷方式等)。
编辑
二、授权级别
- normal-都可以用
- system_basic-需要签名(想表达后期应用上架了需要去申请权限 并且等待审核后才可以用)
- system_core-三方无权
三、授权方式
根据授权方式的不同,权限类型可分为
system_grant(系统授权) 例如网络
如果在应用中申请了system_grant权限,那么系统会在用户安装应用时,自动把相应权限授予给应用。
和
user_grant(用户授权) 例如:麦克风和摄像头
该类型权限不仅需要在安装包中申请权限,还需要在应用动态运行时,通过发送弹窗的方式请求用户授权。在用户手动允许授权后,应用才会真正获取相应权限,从而成功访问操作目标对象。
四、申请应用权限
a. 在配置文件中声明权限
应用需要在module.json5配置文件的requestPermissions标签中声明权限。
{
"module": {
"requestPermissions": [
{
"name" : "ohos.permission.xxxxxx", // 需要使用的权限名称
// 申请权限的原因(用于录制音频)=> 必须去resources/base/element/string.json中声明(注意国际化)
"reason": "$string:reason",
"usedScene": { // 权限使用的场景
"abilities": [
"EntryAbility" // 使用权限的UIAbility或者ExtensionAbility组件的名称
],
"when":"inuse" // 调用时机:inuse-使用时, always-始终
}
},
b. 校验当前是否已经授权 checkAccessToken
在进行权限申请之前,需要先检查当前应用程序是否已经被授予权限。可以通过调用checkAccessToken()方法来校验当前是否已经授权。如果已经授权,则可以直接访问目标操作,否则需要进行下一步操作,即向用户申请授权。
import { abilityAccessCtrl, bundleManager, Permissions } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
const permissions: Array<Permissions> = ['ohos.permission.MICROPHONE'];
async function checkPermissionGrant(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) {
const 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) {
const 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> {
let grantStatus: abilityAccessCtrl.GrantStatus = await checkPermissionGrant(permissions[0]);
if (grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
// 已经授权,可以继续访问目标操作
} else {
// 申请麦克风权限
}
}
c. 动态向用户申请授权 requestPermissionsFromUser
动态向用户申请权限是指在应用程序运行时向用户请求授权的过程。可以通过调用requestPermissionsFromUser()方法来实现。该方法接收一个权限列表参数,例如位置、日历、相机、麦克风等。用户可以选择授予权限或者拒绝授权。
可以在UIAbility的onWindowStageCreate()回调中调用requestPermissionsFromUser()方法来动态申请权限,也可以根据业务需要在UI中向用户申请授权。
应用在onWindowStageCreate()回调中申请授权时,需要等待异步接口loadContent()/setUIContent()执行结束后或在loadContent()/setUIContent()回调中调用requestPermissionsFromUser(),否则在Content加载完成前,requestPermissionsFromUser会调用失败。
应用在UIExtensionAbility申请授权时,需要在onWindowStageCreate函数执行结束后或在onWindowStageCreate函数回调中调用requestPermissionsFromUser(),否则在ability加载完成前,requestPermissionsFromUser会调用失败。
import { abilityAccessCtrl, common, Permissions } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
const permissions: Array<Permissions> = ['ohos.permission.MICROPHONE'];
// 使用UIExtensionAbility:将common.UIAbilityContext 替换为common.UIExtensionContext
function reqPermissionsFromUser(permissions: Array<Permissions>, context: common.UIAbilityContext): void {
let atManager: abilityAccessCtrl.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: BusinessError) => {
console.error(`Failed to request permissions from user. Code is ${err.code}, message is ${err.message}`);
})
}
@Entry
@Component
struct Index {
aboutToAppear() {
// 使用UIExtensionAbility:将common.UIAbilityContext 替换为common.UIExtensionContext
const context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
reqPermissionsFromUser(permissions, context);
}
build() {
// ...
}
}
d. 处理授权结果 requestPermissionOnSetting
如果用户授权,则可以继续访问目标操作。
如果用户拒绝授权,则需要提示用户必须授权才能访问当前页面的功能,并引导用户到系统应用“设置”中打开相应的权限。
import { abilityAccessCtrl, common, bundleManager, Permissions } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
// 三、 用户点击禁止之后 下次只能去应用授权页授权(手动打开权限)
function createPermissionsAgain(context:common.UIAbilityContext) {
AlertDialog.show(
{
title: '温馨提示',
message: '必须要授权才能使用,是否前往应用进行授权',
autoCancel: false,
alignment: DialogAlignment.Bottom,
gridCount: 4,
primaryButton: {
value: '取消',
action: () => {
console.warn('qf 用户再次取消授权')
}
},
secondaryButton: {
value: '前往授权',
action: () => {
// https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/request-user-authorization-second-V5
// 二次向用户申请授权
// const context = getContext(this) as common.UIAbilityContext;
context.startAbility({
bundleName: 'com.huawei.hmos.settings', //设置应用bundleName
abilityName: 'com.huawei.hmos.settings.MainAbility', //设置应用abilityName
uri: "application_info_entry", //通知管理页面
parameters: {
pushParams: context.abilityInfo.bundleName
}
}).then((data) => {
console.info('qf 前往授权页面成功', JSON.stringify(data))
}).catch((error:Object) => {
console.error('qf 前往授权页面失败', JSON.stringify(error))
})
}
}
}
)
}
async function checkPermissionGrant(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) {
const 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) {
const 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(permission: Permissions): Promise<boolean> {
// const grantStatus: boolean = await checkPermissionGrant('ohos.permission.MICROPHONE') === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;// 获取精确定位权限状态
const grantStatus: boolean = await checkPermissionGrant(permission) === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;// 获取精确定位权限状态
return grantStatus
}
// 使用UIExtensionAbility:将common.UIAbilityContext 替换为common.UIExtensionContext
async function reqPermissionsFromUser(permissions: Array<Permissions>, context: common.UIAbilityContext): Promise<boolean> {
let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
// requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗
return 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) {
// 用户授权,可以继续访问目标操作
return true
} else {
createPermissionsAgain(context) // 打开设置页面,现在还未升级了 通过app内的弹出层提高用户体验度
return false
// // 用户拒绝授权,提示用户必须授权才能访问当前页面的功能,并引导用户到系统设置中打开相应的权限
// let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
// // let context: Context = getContext(this) as common.UIAbilityContext;
// // atManager.requestPermissionOnSetting(context, ['ohos.permission.APPROXIMATELY_LOCATION']).then((data: Array<abilityAccessCtrl.GrantStatus>) => {
// return atManager.requestPermissionOnSetting(context, permissions).then((data: Array<abilityAccessCtrl.GrantStatus>) => {
// console.info('data:' + JSON.stringify(data));
// // return true
// return data[0] === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED
// }).catch((err: BusinessError) => {
// console.error('data:' + JSON.stringify(err));
// return false
// });
// return false;
}
}
return false
// 授权成功
}).catch((err: BusinessError) => {
console.error(`Failed to request permissions from user. Code is ${err.code}, message is ${err.message}`);
return false
})
}
@Entry
@Component
struct Test {
build() {
Button('录音').onClick(async () => {
// 方案1:直接调用录音的API ❌ 得看下权限有没有,如果没有去申请
// 方案2:检查是否授权 有-直接用API,没有申请权限
const state = await checkPermissions('ohos.permission.MICROPHONE')
console.log('2 检查是否授权 ', state)
if (!state) {
// 3 动态获取权限
const state2 = await reqPermissionsFromUser(['ohos.permission.MICROPHONE'], getContext(this) as common.UIAbilityContext);
console.log('3 动态获取权限 ', state2)
if (!state2) return
}
// 调用录音API实现各种功能
console.log('调用录音API实现各种功能')
})
}
}
e. 封装权限管理
- 封装
import { abilityAccessCtrl, bundleManager, Permissions } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
class PermissionUtil {
// 1 检查是否有权限 2个方法
async checkPermissionGrant(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) {
const 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) {
const err: BusinessError = error as BusinessError;
console.error(`Failed to check access token. Code is ${err.code}, message is ${err.message}`);
}
return grantStatus;
}
async checkPermissions(permission: Permissions[], context: Context): Promise<boolean> {
const bool: boolean = await this.checkPermissionGrant(permission[0]) === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;// 获取精确定位权限状态。
// return bool 优化下
if (!bool) return await this.reqPermissionsFromUser(permission, context)
return true
}
// 2 动态申请权限
async reqPermissionsFromUser(permissions: Array<Permissions>, context: Context): Promise<boolean> {
let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
// requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗
return atManager.requestPermissionsFromUser(context, permissions).then(async (data) => {
let grantStatus: Array<number> = data.authResults;
let length: number = grantStatus.length;
for (let i = 0; i < length; i++) {
if (grantStatus[i] === 0) {
// 用户授权,可以继续访问目标操作
// return true
} else {
// 用户拒绝授权,提示用户必须授权才能访问当前页面的功能,并引导用户到系统设置中打开相应的权限
// return false;
return await this.onSetting(permissions, context);
}
}
// 授权成功
return true
}).catch((err: BusinessError) => {
return false
console.error(`Failed to request permissions from user. Code is ${err.code}, message is ${err.message}`);
})
}
// 3 再次申请权限 拉起一个窗口
onSetting(permissions: Permissions[], context:Context):Promise<boolean> {
let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
// let context: Context = getContext(this) as common.UIAbilityContext;
return atManager.requestPermissionOnSetting(context, permissions).then((data: Array<abilityAccessCtrl.GrantStatus>) => {
console.info('data:' + JSON.stringify(data));
return data[0] === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;
}).catch((err: BusinessError) => {
return false
console.error('data:' + JSON.stringify(err));
});
}
}
export const permissionUtil = new PermissionUtil()
- 使用
import { permissionUtil } from '../utils/PermissionUtil'
@Entry
@Component
struct Index {
build() {
Button('录音').onClick(async (event: ClickEvent) => {
const state = await permissionUtil.checkPermissions(['ohos.permission.MICROPHONE'], getContext())
if (!state) return
console.log('录音API')
})
}
}
五、受限开放权限
编辑
a. 在配置文件中声明权限
module.json5
{
"module": {
"requestPermissions": [
....
继续追加
// 受限开放权限 ---------------------------------
// 通讯录(权限组-读写)
{
"name": "ohos.permission.READ_CONTACTS",
"reason": '$string:permission_reason_contact',
"usedScene": {}
},
{
"name": "ohos.permission.WRITE_CONTACTS",
"reason": '$string:permission_reason_contact',
"usedScene": {}
},
// 图片或视频文件(权限组-读写 仅应用需要克隆、备份或同步图片/视频类文件可申请) 其他场景用使用Picker选择媒体库资源。
{
"name": "ohos.permission.READ_IMAGEVIDEO",
"reason": "$string:permission_reason_imageVideo",
"usedScene": {}
},
{
"name": "ohos.permission.WRITE_IMAGEVIDEO",
"reason": "$string:permission_reason_imageVideo",
"usedScene": {}
}
// 受限开放权限 ---------------------------------
b. 自动签名
配置好声明权限,直接模拟器运行会报错,因为这些权限需要签名(目的就是告诉开发者这些权限不是说直接就可以用,上架的时候需要审批通过才可以用)
developer.huawei.com/consumer/cn…
配置完在运行就没事了,
然后就可以使用测试
六、附言
APL(Ability Privilege Level)应用的权限申请优先级的定义,不同等级的应用能够申请的权限等级不同。
ACL(Access Control List)访问控制列表 (也就是受限开放权限里面的权限,老手册会有标记现在新的手册没有了)
AGC(AppGallery Connect) 应用市场
权限级别:normal-都可以用、system_basic-需要签名、system_core-三方无权 (APL表达的思想)
授权方式:
- system_grant系统授权-用户安装应用时自动授权后续无感:internet网络/蓝牙配置/应用基本信息/.../身份认证/Wi-Fi信息/陀螺仪传感器信息等等
- user_grant用户授权-弹框:相机/位置信息/媒体文件/麦克风/日历/用户运动状态/设备数据交换/用户健康等等
应用在访问数据或者执行操作时,需要评估该行为是否需要应用具备相关的权限。如果确认需要目标权限,则需要在应用安装包中申请目标权限。每一个权限的权限等级、授权方式不同,申请权限的方式也不同,开发者在申请权限前,需要先根据以下流程判断应用能否申请目标权限。
编辑
上图的数字标注,请参考以下说明:
-
标注1:应用APL等级与权限等级的匹配关系请参考APL等级说明。
-
标注2:权限的授权方式分为user_grant(用户授权)和system_grant(系统授权),具体请参考授权方式说明。
-
如果目标权限是system_grant类型,开发者在进行权限申请后,系统会在安装应用时自动为其进行权限预授予,开发者不需要做其他操作即可使用权限。
-
在应用需要获取user_grant权限时,请完成以下步骤:
- 在配置文件中,声明应用需要请求的权限。
- 将应用中需要申请权限的目标对象与对应目标权限进行关联,让用户明确地知道,哪些操作需要用户向应用授予指定的权限。
- 运行应用时,在用户触发访问操作目标对象时应该调用接口,精准触发动态授权弹框。该接口的内部会检查当前用户是否已经授权应用所需的权限,如果当前用户尚未授予应用所需的权限,该接口会拉起动态授权弹框,向用户请求授权。
- 检查用户的授权结果,确认用户已授权才可以进行下一步操作。
-
-
标注3:应用可以通过ACL(访问控制列表)方式申请高级别的权限,具体请参考申请使用受限权限。
七、企业级面试题
鸿蒙授权方式、授权级别有哪些
申请权限步骤
a 在配置文件中声明权限
b 校验当前是否已经授权 checkAccessToken
c 动态向用户申请授权 requestPermissionsFromUser
d 处理授权结果 requestPermissionOnSetting 如果用户授权,则可以继续访问目标操作。 如果用户拒绝授权,则需要提示用户必须授权才能访问当前页面的功能,并引导用户到系统应用“设置”中打开相应的权限。 在common架构base模块下封装授权文件
developer.huawei.com/consumer/cn…
使用ACL/受限制开放权限如何申请
developer.huawei.com/consumer/cn…
编辑
八、鸿蒙安全 - 全新的架构与机制,将隐私控制权交还给用户
首先,鸿蒙 NEXT 全面梳理了所有系统授权,取消了 9 类不合理权限,有效避免了潜在的隐私泄露风险。这些权限包括读取已安装应用列表、访问短信、访问存储文件、创建桌面快捷方式、获取设备信息、显示悬浮窗、访问通话记录、应用内安装应用以及拨打电话和管理通话。
编辑
其次,鸿蒙 NEXT 改变了传统的权限控制逻辑,从“管权限”转变为“管数据” ,从而为用户提供更高级别的隐私保护。例如,在使用支付宝或小红书等第三方应用进行拍照或扫码时,传统系统需要调用摄像头权限完成此次操作,拍摄或扫码时,应用可以看到整个过程中摄像头实时扫过的画面。而在鸿蒙 NEXT 上,当用户在第三方应用中点击拍摄时,系统提示变为“您的摄像头正在取景,但应用仅可访问拍摄完成的图片”。
鸿蒙 NEXT 这一创新的安全访问机制,涵盖了位置、文件、图库、相机、联系人、剪贴板和音频 7 个方面,这些高频使用场景,也是用户隐私风险泄露的高发区。通过精准性、单次性的权限授予方式,确保了最小化的数据访问,大大降低了用户隐私泄露的风险。可以说是把隐私控制权完全交还给用户,让用户能够真正掌控。
编辑
举个简单的例子,假设用户的隐私数据是家中的珍贵物品,而“权限”则如同一把把房间钥匙。过去,当用户点击“允许”授权时,实际上是交出了钥匙,应用便可以自由进入该房间并取用其中的物品。然而,在 HarmonyOS NEXT 上,情况发生了变化。应用只能收到用户授权的具体物品,房间里的其他东西,都与之隔绝。例如修改头像选择照片
编辑
✨家人们点个juejin账号关注,会持续发布大前端领域技术文章💕 🍃
✨家人们点个juejin账号关注,会持续发布大前端领域技术文章💕 🍃
✨家人们点个juejin账号关注,会持续发布大前端领域技术文章💕 🍃
^_^ 点关注、不迷路、主播带你学技术 (๑′ᴗ‵๑)I Lᵒᵛᵉᵧₒᵤ❤