权限与安全
权限总览
RunAdvisor 共声明 5 项权限:
| 权限 | 类型 | 用途 | 声明位置 |
|---|---|---|---|
| ohos.permission.INTERNET | normal | 天气 API 网络请求 | module.json5 |
| ohos.permission.APPROXIMATELY_LOCATION | user_grant | 模糊位置获取 | module.json5 |
| ohos.permission.LOCATION | user_grant | 精确位置获取 | module.json5 |
| ohos.permission.ACTIVITY_MOTION | user_grant | 计步器数据读取 | module.json5 |
| ohos.permission.GYROSCOPE | user_grant | 陀螺仪数据读取 | module.json5 |
module.json5 配置
所有权限在 entry/src/main/module.json5 的 requestPermissions 中声明:
"requestPermissions": [ { "name": "ohos.permission.INTERNET", "reason": "$string:perm_internet_reason", "usedScene": { "abilities": ["EntryAbility"], "when": "inuse" } }, { "name": "ohos.permission.APPROXIMATELY_LOCATION", "reason": "$string:perm_location_reason", "usedScene": { "abilities": ["EntryAbility"], "when": "inuse" } }, { "name": "ohos.permission.LOCATION", "reason": "$string:perm_location_reason", "usedScene": { "abilities": ["EntryAbility"], "when": "inuse" } }, { "name": "ohos.permission.ACTIVITY_MOTION", "reason": "$string:perm_step_reason", "usedScene": { "abilities": ["EntryAbility"], "when": "inuse" } }, { "name": "ohos.permission.GYROSCOPE", "reason": "$string:perm_gyro_reason", "usedScene": { "abilities": ["EntryAbility"], "when": "inuse" } } ]reason 字符串资源
在 entry/src/main/resources/base/element/string.json 中定义:
| 资源名 | 值 |
|---|---|
| perm_internet_reason | Used to fetch weather data for run advice |
| perm_location_reason | Used to get your location for route recommendations |
| perm_step_reason | Used to read step count for health tracking |
| perm_gyro_reason | Used to enhance location accuracy with gyroscope data |
运行时权限处理
传感器权限 (GYROSCOPE / ACTIVITY_MOTION)
传感器调用在 try-catch 中执行,权限拒绝时优雅降级:
// LocationEnhanceService.startGyroscope()try{sensor.on(sensor.SensorId.GYROSCOPE,callback,options);this.listening=true;}catch(e){this.gyroAvailable=false;// 降级处理}// StepService.querySensorSteps()try{sensor.once(sensor.SensorId.PEDOMETER,callback);}catch(e){resolve(0);// 返回 0 步}定位权限 (LOCATION)
LocationService.getCurrentLocation() 在 try-catch 中执行,失败时返回默认北京坐标:
asyncgetCurrentLocation():Promise<number[]>{try{constlocation=awaitgeoLocationManager.getCurrentLocation(requestInfo);return[location.latitude,location.longitude];}catch(e){return[Constants.BEIJING_LAT,Constants.BEIJING_LNG];// 默认北京}}网络权限 (INTERNET)
WeatherService 网络请求在 try-catch 中执行,失败时返回默认天气:
asyncgetWeather(lat,lng):Promise<WeatherInfo>{try{constresponse=awaitrequest.request(url,options);// 解析天气数据}catch(e){// 降级到默认天气}returnthis.getDefaultWeather();// 北京 22°C 晴}安全注意事项
- API Key 安全: 天气 API Key 使用占位符 “YOUR_KEY”,生产环境应通过安全后端代理
- 数据存储: 用户数据通过 Preferences 本地存储,不涉及网络传输
- 位置数据: 位置数据仅在本地使用,不上传到任何服务器
- 签名配置: 当前未配置签名 (Will skip sign ‘hos_hap’),真机部署需要在 DevEco Studio 中手动配置
权限分级详解
normal 级别权限
INTERNET 权限属于 normal 级别,安装时自动授予,无需用户确认:
{ "name": "ohos.permission.INTERNET", "reason": "$string:perm_internet_reason", "usedScene": { "abilities": ["EntryAbility"], "when": "inuse" } }normal 级别权限的风险较低,仅允许应用发起网络请求,不涉及用户敏感数据。reason 字段仍需提供,作为应用商店审核的依据。usedScene.when = “inuse” 表示权限仅在前台使用时生效。
user_grant 级别权限
其余 4 项权限均为 user_grant 级别,需要用户在运行时主动授权:
| 权限 | 授权时机 | 拒绝后果 |
|---|---|---|
| APPROXIMATELY_LOCATION | 首次使用位置时弹窗 | 无法获取模糊位置 |
| LOCATION | 首次使用精确位置时弹窗 | 无法获取精确位置 |
| ACTIVITY_MOTION | 首次读取步数时弹窗 | 步数始终为 0 |
| GYROSCOPE | 首次访问陀螺仪时弹窗 | 位置增强不可用 |
HarmonyOS 的 user_grant 权限机制与 Android 类似,首次调用受保护 API 时系统自动弹出授权弹窗。用户拒绝后,应用应优雅降级而非反复请求。
权限声明格式
每项权限的声明包含三个必要字段:
{ "name": "ohos.permission.LOCATION", "reason": "$string:perm_location_reason", "usedScene": { "abilities": ["EntryAbility"], "when": "inuse" } }- name: 权限标识符,必须与 HarmonyOS 权限列表中的名称完全一致
- reason: 向用户解释为何需要该权限的字符串资源引用,不能为空
- usedScene: 描述权限的使用场景,abilities 指定使用该权限的 Ability,when 指定使用时机
reason 字段必须使用 $string 引用而非硬编码字符串,这是 HarmonyOS 的编译要求。直接写字符串会导致构建失败。
reason 字符串资源详解
string.json 配置
权限说明字符串定义在 entry/src/main/resources/base/element/string.json 中:
{"string":[{"name":"perm_internet_reason","value":"Used to fetch weather data for run advice"},{"name":"perm_location_reason","value":"Used to get your location for route recommendations"},{"name":"perm_step_reason","value":"Used to read step count for health tracking"},{"name":"perm_gyro_reason","value":"Used to enhance location accuracy with gyroscope data"}]}当前 reason 文本为英文,面向开发者和审核人员。生产环境建议提供中文版本,使用多语言资源目录(如 zh/element/string.json)实现国际化。reason 文本应简明扼要,说明权限的用途和益处,而非技术细节。
reason 文本的审核要求
HarmonyOS 应用商店对 reason 文本有以下要求:
- 必须明确说明权限用途,不能使用模糊描述如"用于应用功能"
- 必须说明权限对用户的价值,如"获取位置用于推荐附近路线"
- 不能包含误导性描述,如"不授权将无法使用应用"(对于非核心功能)
- 文本长度建议 20-100 字符
运行时权限处理策略
优雅降级模式
RunAdvisor 采用"请求但允许拒绝"的权限处理策略,每个受权限保护的 API 调用都包裹在 try-catch 中:
调用受保护 API ├── 成功 → 使用真实数据 └── 异常 → 使用默认数据 ├── LocationService → 北京默认坐标 ├── StepService → 0 步 ├── LocationEnhanceService → GPS 原始坐标 └── WeatherService → 默认天气 (北京 22°C 晴)这种策略的优势:
- 用户不会因拒绝某个权限而无法使用应用
- 应用不会反复弹窗骚扰用户
- 功能降级对用户透明,部分功能使用默认值仍可运行
定位权限的双重声明
APPROXIMATELY_LOCATION 和 LOCATION 同时声明,遵循 HarmonyOS 的定位权限要求:
{ "name": "ohos.permission.APPROXIMATELY_LOCATION", "reason": "$string:perm_location_reason", "usedScene": { "abilities": ["EntryAbility"], "when": "inuse" } }, { "name": "ohos.permission.LOCATION", "reason": "$string:perm_location_reason", "usedScene": { "abilities": ["EntryAbility"], "when": "inuse" } }HarmonyOS 要求精确位置权限 (LOCATION) 必须与模糊位置权限 (APPROXIMATELY_LOCATION) 同时声明。仅声明 LOCATION 而不声明 APPROXIMATELY_LOCATION 会导致编译错误。这一设计遵循了权限最小化原则:应用先获取模糊位置,仅在用户明确授权后才使用精确位置。
传感器权限的使用时机
GYROSCOPE 和 ACTIVITY_MOTION 权限在应用启动时即开始使用:
// EntryAbility.onCreate()PreferencesService.init(this.context);LocationEnhanceService.getInstance().startGyroscope();startGyroscope() 在 onCreate 中调用,此时应用尚未显示 UI,但传感器数据需要提前订阅以确保首页加载时已有陀螺仪数据。如果延迟到首页显示后再订阅,首次 enhance() 调用时 gyroAvailable 为 false,会直接返回 GPS 原始坐标,错失滤波效果。
权限拒绝后的用户体验
| 权限被拒绝 | 用户可见影响 | 补偿措施 |
|---|---|---|
| INTERNET | 天气始终为默认值 | 显示"北京 22°C 晴"作为占位 |
| APPROXIMATELY_LOCATION | 位置始终为北京 | 路线功能仍可使用北京路线 |
| LOCATION | 同上 | 同上 |
| ACTIVITY_MOTION | 步数始终为 0 | 可通过饮食记录追踪热量 |
| GYROSCOPE | 位置增强不可用 | 路线功能不受影响 |
安全分析
API Key 安全
当前天气 API Key 为占位符,存在以下安全隐患:
staticreadonlyWEATHER_API_KEY:string='YOUR_KEY';如果替换为真实的 OpenWeatherMap API Key 并直接写在源码中,存在以下风险:
- APK 反编译后 Key 明文暴露
- Key 可被第三方提取用于非授权调用
- OpenWeatherMap 免费版有调用频率限制,Key 泄露可能导致配额耗尽
推荐的 Key 安全方案:
| 方案 | 安全等级 | 实现复杂度 | 说明 |
|---|---|---|---|
| 后端代理服务 | 高 | 中 | 应用请求自有服务器,服务器转发天气 API |
| 加密存储 Key | 中 | 低 | Key 加密后存储,运行时解密 |
| HarmonyOS 凭据管理 | 高 | 高 | 使用 HarmonyOS Credential Manager API |
| 环境变量注入 | 中 | 中 | 构建时从环境变量注入,不进入源码 |
本地数据安全
用户数据通过 Preferences 本地存储,安全特性分析:
数据隔离: HarmonyOS 应用沙箱机制确保每个应用的 Preferences 数据对其他应用不可见。即使设备上有恶意应用,也无法读取 RunAdvisor 的用户数据。
数据内容: Preferences 中存储的数据包括:
- 用户资料 (体重、身高、年龄、性别)
- 健身目标 (步数目标、热量目标、跑步目标)
- 缓存数据 (步数、热量、天气缓存)
- 状态标志 (is_profile_set)
这些数据均为非敏感的健康和运动信息,不涉及密码、银行卡等高敏感数据。
删除行为: 卸载应用时 Preferences 数据随应用沙箱一起删除,不存在数据残留问题。
位置数据安全
位置数据的处理遵循最小化原则:
- 仅在需要时获取: getCurrentLocation() 只在首页加载和天气查询时调用,不持续追踪
- 不持久化位置: 原始 GPS 坐标和增强后坐标均不写入 Preferences
- 不上传位置: 位置数据仅用于本地天气查询,不发送到任何服务器
- 增强后丢弃: enhance() 返回增强坐标后,原始坐标不再保留
网络通信安全
应用唯一的网络请求来自天气 API 调用:
consturl:string=`https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lng}&appid=${Constants.WEATHER_API_KEY}&units=metric`;constresponse=awaitrequest.request(url,{method:'GET',connectTimeout:5000,readTimeout:5000});安全特性:
- 使用 HTTPS 协议加密传输
- 请求包含用户位置坐标(经纬度),但不包含其他个人信息
- 超时限制(5 秒)防止网络请求阻塞应用
- 请求失败时自动降级到默认天气数据
代码层面安全
变量声明安全
ArkTS 严格模式要求所有变量在声明时初始化,防止使用未定义变量导致的安全隐患:
@StatestepCount:number=0;@StatetodayCalories:number=0;@StateisFirstLaunch:boolean=false;类型安全
ArkTS 的强类型系统防止了类型混淆攻击:
constweight:number=parseInt(this.setupWeight);if(isNaN(weight))return;显式类型声明和运行时类型检查(parseInt + isNaN 验证)双重保障数据类型正确性。
构建警告与安全
GYROSCOPE 权限警告
构建时出现警告:
WARN: ohos.permission.GYROSCOPE is a user_grant permission, please ensure it is requested at runtime此警告提醒开发者 GYROSCOPE 是 user_grant 权限,需要在运行时请求。当前应用通过 try-catch 处理权限拒绝场景,已满足要求。此警告不影响构建和运行。
签名警告
WARN: Will skip sign 'hos_hap' (no signing config)签名缺失的影响:
- 无法在真机上安装运行
- 无法发布到应用商店
- 不影响模拟器调试
签名配置步骤:
- 在 DevEco Studio 中打开 File > Project Structure > Project > Signing Configs
- 勾选 “Automatically generate signature”
- 登录华为开发者账号
- 自动生成调试签名证书
权限最佳实践
1. 最小权限原则
RunAdvisor 仅声明了 5 项必要权限,未申请任何冗余权限:
- 不申请 CAMERA(无拍照功能)
- 不申请 MICROPHONE(无语音功能)
- 不申请 READ_WRITE_DOWNLOAD_DIRECTORY(不操作文件系统)
- 不申请 KEEP_BACKGROUND_RUNNING(无后台跑步追踪)
2. 运行时请求时机
权限请求的最佳时机是在用户即将使用相关功能时,而非应用启动时。RunAdvisor 当前在 onCreate 中即启动陀螺仪订阅,不完全符合此原则。优化方案是将 startGyroscope() 延迟到首页加载时调用,但需权衡延迟启动对首页数据加载的影响。
3. 权限说明透明性
每个权限的 reason 文本都清晰说明了权限用途和用户价值,符合 HarmonyOS 应用商店的审核要求。
使用场景
场景一:全新安装的权限流程
用户首次安装并打开 RunAdvisor:
- 应用启动,INTERNET 权限自动授予
- 首页加载,调用 getCurrentLocation(),系统弹出 LOCATION 权限授权弹窗
- 用户点击"允许",应用获取精确位置,查询天气
- 步数功能触发 ACTIVITY_MOTION 权限弹窗
- 用户点击"拒绝",步数显示 0,其他功能正常
场景二:权限被拒绝后的体验
用户在设置中关闭了 LOCATION 权限:
- 首页加载时 getCurrentLocation() 抛出异常
- 应用降级使用北京默认坐标
- 天气显示"北京 22°C 晴"
- 路线功能正常(路线数据不依赖实时位置)
- 用户无感知降级,应用功能完整
场景三:安全审计
应用商店审核人员检查 RunAdvisor 的权限声明:
- 5 项权限各有明确的 reason 说明
- usedScene.when = “inuse” 确保权限仅在前台使用
- 无后台位置追踪、无联系人读取等高风险权限
- API Key 为占位符,不存在密钥泄露风险
- 审核通过