端侧安全的整体图景
端侧安全不是某一个功能,而是一整条链:你申请的权限是否必要、拿到权限后用得是否克制、落盘的数据是否加密、网络传输是否可信,任何一环松懈,用户数据都可能泄露。鸿蒙的安全设计围绕"最小可用"展开——只给应用够用的权限,且使用过程对用户透明。
对开发者而言,最常打交道的是三块:权限申请、本地数据保护、网络通信。前两块做不好,审核很容易被拒;后一块做不好,即使审核通过,线上也会出事故。这篇按这个顺序展开。
安全不是加几道加密就完事,而是从"这个权限到底要不要"开始克制。能用系统能力替代私有数据采集的,就别申请权限。
权限模型:normal 与 system_grant
鸿蒙把权限分成几个等级。normal 类权限在安装时默认授予,不需要运行时弹窗,比如网络访问;而那些涉及隐私的敏感权限,属于 user_grant,必须在用到时动态申请,由用户明确授权。
- normal风险低,安装即授予,配置在 module.json 里声明即可。
- user_grant涉及隐私,必须运行时申请,且要先在配置里声明。
- 声明先行任何权限都要先在配置文件里声明,运行时申请才有意义。
- 最小化只申请功能真正需要的权限,多余权限会在审核中被要求说明。
常见的 user_grant 权限包括定位、相机、麦克风、读取媒体文件、通讯录等。每次申请前都要想清楚:这个功能离开这项权限能不能做?能替代就别申请。
运行时权限申请流程
运行时申请的标准流程是:先检查是否已有权限,没有再发起请求,根据用户的选择决定后续路径。关键是不要一进应用就弹窗要权限,而是在用户触发相关功能的那一刻才申请。
import { abilityAccessCtrl } from '@kit.AbilityKit';
import { bundleManager } from '@kit.AbilityKit';
const atManager = abilityAccessCtrl.createAtManager();
async function ensureLocation(): Promise<boolean> {
const ctx = getContext(this);
const status = await atManager.checkAccessToken(
bundleManager.getInfoSelf(ctx).accessTokenId,
'ohos.permission.APPROXIMATELY_LOCATION'
);
if (status === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
return true;
}
// 弹系统授权框;用户拒绝后要给出引导,而非反复弹窗
const res = await atManager.requestPermissionsFromUser(ctx,
['ohos.permission.APPROXIMATELY_LOCATION']);
return res.authResults[0] === 0;
}
用户拒绝后正确的做法是:解释这项权限为什么必要,并引导用户去系统设置里手动开启,而不是在页面里反复弹系统框——那样只会加速卸载。
敏感权限的申请话术
用户看到系统授权框时往往一头雾水,所以在弹窗前,应该先用一段友好的说明告诉用户"我们要做什么、为什么需要这项权限、数据会怎么用"。这段说明是提升授权率和过审的关键。
- 说清用途:用一句话把功能和权限对应起来,例如"用于显示附近的门店"。
- 说清边界:说明数据是否上传、是否共享,让用户心里有数。
- 给退路:允许用户拒绝后仍能用大部分功能,而不是一刀切除。
- 不欺骗:说明文案要和实际行为一致,审核时会对照检查。
数据加密与密钥管理
拿到敏感数据后,落盘这一步要加密。鸿蒙提供了基于应用沙箱与密钥管理的能力,密钥由系统托管,开发者不需要自己把密钥写进代码。
把密钥硬编码进安装包,等于把家门钥匙贴在门上。任何能反编译 APK 的人都能取走它。
实践要点:敏感字段加密后再写库;日志里绝不打印明文;登出与卸载时清理对应数据。这和上一篇数据持久化里提到的加密落盘是同一套思路——存储负责"存得下",安全负责"存得安全"。
网络通信加固
网络这一层最基本的要求是全程 HTTPS,不在明文 HTTP 上传输任何敏感信息。进阶做法是证书固定(证书校验),防止中间人用伪造证书劫持流量。
另外,对服务端返回的数据永远不要完全信任:客户端校验、服务端也要校验,输入长度、类型、范围都要在边界处挡住。上传接口要加频率限制与文件类型白名单,否则很容易被刷量或塞入恶意文件。
实战:定位权限的合规申请
把前面的流程串起来:用户点击"查看附近"时,先展示一段说明,再检查权限,没有则申请,被拒绝则引导设置。
async onNearbyClick() {
const granted = await ensureLocation();
if (!granted) {
this.showGuide('需要定位才能显示附近内容,可在系统设置中开启');
return;
}
this.loadNearby(); // 授权成功,再发起定位与请求
}
// 系统设置引导(伪代码):跳转到本应用权限页
private showGuide(msg: string) {
promptAction.showToast({ message: msg });
// 实际项目里用 UIAbility 的 want 跳转到系统权限设置
}
整个交互的核心是"需要时才出现":用户还没表达定位意图,权限弹窗就不该出现。把申请时机放在动作发生的那一刻,授权率和用户好感都会明显提升。
上架前的安全自查清单
提交上架前,对照这份清单走一遍。
- 权限清单:逐项核对是否每个权限都有明确用途,能否砍掉。
- 隐私政策:说明采集了哪些数据、用于什么、是否共享。
- 敏感字段:确认已加密落盘,日志已脱敏。
- 网络通信:确认全程 HTTPS,关键接口有防刷与校验。
- 拒绝路径:确认用户拒绝权限后应用仍能正常使用核心功能。
安全这件事,平时不显山露水,一旦出事就是大事。把它当成和功能开发同等重要的一环,而不是上架前补的作业。