“App能做的我都能做?”——别闹!鸿蒙设备安全有三道硬防线:沙箱、权限、进程隔离,你绕不过去的!
《鸿蒙安全机制解析:沙箱、权限与进程隔离的三重防护》探讨了鸿蒙系统如何通过沙箱机制、权限管理和进程隔离构建多层次安全防护。文章从内核层到应用层剖析了安全设计原理:1)沙箱机制通过独立UID/GID、私有存储和资源视图限制应用边界;2)权限管理系统采用AccessToken体系实现声明式权限申请与运行时动态核验;3)进程隔离模型结合SELinux强制访问控制,确保IPC/RPC通信的安全边界。文中提
我是兰瓶Coding,一枚刚踏入鸿蒙领域的转型小白,原是移动开发中级,如下是我学习笔记《零基础学鸿蒙》,若对你所有帮助,还请不吝啬的给个大大的赞~
前言
直说吧:安全不是“加个 if”,而是“从内核到框架的层层约束”。每次有人问我“能不能让 A 应用直接读 B 应用的数据”“能不能在后台静默干点小动作”,我都会先反问一句:你觉得鸿蒙设备的三大护城河——沙箱机制、权限管理、进程隔离——是摆设吗?
这篇就按你给的大纲,从下到上把鸿蒙(HarmonyOS/OpenHarmony 生态)里的安全秩序掰开揉碎:先讲沙箱机制(你能碰到的“墙”在哪)、再到权限管理(墙门怎么开、钥匙谁发)、最后是进程隔离(进墙还要过人——IPC/RPC 怎么把关)。中途穿插两位主角:AccessToken(令牌与权限核验)、SELinux(内核强制访问控制)。不空谈,给你能落地的代码片段与项目级套路。走起!💪
0. 全景脑图:三道防线如何协同?
┌────────────── 内核层 ──────────────┐
│ SELinux(强制访问控制) + 命名空间/UID/GID │
└───────────────┬──────────────────┘
│
┌───────▼─────────┐
│ 进程隔离/IPC │ ← IRemoteObject/Binder风格对象、系统服务只认“令牌+域”
└───────┬─────────┘
│
┌───────▼─────────┐
│ 应用沙箱 │ ← 独立文件区、私有内存、受限系统资源视图
└───────┬─────────┘
│
┌───────▼─────────┐
│ 权限管理 │ ← AccessToken授予/校验、动态授权、人机确认
└──────────────────┘
口诀:内核管“到底可不可以”,框架管“凭什么可以”,应用再谈“我想怎么用”。
1. 沙箱机制:别想“越门而入”,路径、资源、进程都是“各扫门前雪”
1.1 应用级沙箱的边界
- 独立UID/GID:每个应用安装时分配独立身份;文件、套接字、内核对象都按 UID/GID 判边界。
- 私有存储:每个应用有自己的私有目录(示例:应用数据/缓存/临时),默认他人不可读写。
- 系统能力视图受限:即使同在一台设备,进程看到的可用资源目录、系统服务接口也因权限差异而不同。
- 跨应用数据访问:必须走受管道的通路(如 DataShare/分布式数据、受控文件选择、系统服务接口等),直接读路径不行。
1.2 SELinux:内核级“铁闸门”
- MAC(Mandatory Access Control):即便进程拥有“传统 POSIX 权限”,策略不放行也不行。
- 域与类型:进程被标记为某个 域(domain),文件/设备/套接字有 类型(type);策略定义“哪个域允许访问哪些类型,以何种方式”。
- 默认拒绝:没有明文允许,就视为拒绝(deny by default);策略收紧、防止“想当然”。
通俗点:SELinux 是系统的“最小特权裁判”,它不在乎你是谁,只在乎“这条规则有没有写你能做这事”。
1.3 对开发的直接影响
- 你无法“跨目录偷文件”,除非通过系统批准的接口;
- 你要用相机/麦克风/蓝牙/分布式数据?先提权限,再走被允许的 IPC;
- 私下起“万能后门”去碰系统资源?SELinux 一棒子打回去。
2. 权限管理:AccessToken 是“票面”,授权与核验是一套流程
2.1 权限的分类(把“钥匙”分等级)
- 普通权限(user_grant):用户允许即可使用(如读写媒体等),有的需要运行时弹窗。
- 系统/签名权限(system_grant/signature):仅系统应用或与系统签名一致的应用可获;普通三方应用别想绕。
- 敏感权限:摄像头、麦克风、位置信息、后台活动等,通常需要运行时确认与可撤销。
2.2 声明与请求(module.json5 & 运行时)
在 module.json5 声明(示例):
{
"module": {
"name": "entry",
"type": "entry",
"abilities": [ { "name": "EntryAbility", "type": "page", "srcEntry": "./ets/entryability/EntryAbility.ets" } ],
"requestPermissions": [
{ "name": "ohos.permission.CAMERA" },
{ "name": "ohos.permission.MICROPHONE" },
{ "name": "ohos.permission.READ_MEDIA" },
{ "name": "ohos.permission.WRITE_MEDIA" }
]
}
}
运行时请求(ArkTS):
// 运行时动态请求(示意)
import abilityAccessCtrl from '@ohos.abilityAccessCtrl'
export async function ensurePermissions(perms: string[]) {
const at = abilityAccessCtrl.createAtManager()
// 某些版本API需要传入UIAbilityContext;请以当前SDK为准
await at.requestPermissionsFromUser(getContext(this) as any, perms)
}
要点:声明是“我要”,运行时同意是“我可以”。两者缺一不可。
2.3 AccessToken:谁来核验“你到底能不能”
-
系统在安装/授权时,为应用分配一个 AccessToken(令牌),编码了已获权限、可信等级等信息。
-
校验入口:被调用方(系统服务/你自己的后台服务Ability)通常要在处理请求前主动校验对方令牌:
- 拿到调用者的 TokenID;
- 调
verify/check方法校验某权限; - 决定是否继续执行。
服务端校验示例(伪代码,按 SDK 适配):
import accessToken from '@ohos.security.accessToken'
// 在你的 ServiceExtensionAbility / RPC 桩里
function assertCallerHas(permission: string) {
// 1. 拿到远端调用方的 token ID
const callerTokenId = accessToken.getCallingTokenID()
// 2. 核验权限
const ret = accessToken.verifyAccessToken(callerTokenId, permission)
if (ret !== accessToken.GrantStatus.PERMISSION_GRANTED) {
throw new Error(`Permission denied: ${permission}`)
}
}
为什么要在服务端再验一次?
- 因为调用入口才是最终“守门人”。即使客户端自称有权限,以服务端验证为准。
- 这就是“零信任”的基本姿势:不相信来路,只相信令牌。
2.4 最佳实践清单
- 最小权限:只声明你真的需要的;
- 分场景请求:在“用到时”弹请求,而不是一进来就要一箩筐;
- 失败兜底:被拒绝时,提供“解释—再次申请—降级处理”的完整链路;
- 服务端二次校验:只要走 IPC,就在被调端用 AccessToken 再查一遍。
3. 进程隔离:就算“进了门”,也要过“人”和“章”
3.1 进程级隔离模型
- 每个 Ability(UIAbility、ServiceExtensionAbility 等)运行在进程中;默认同应用同进程,必要时可多进程化。
- 系统服务在受控域内运行,不与普通应用同权同域。
- 内核/SELinux继续兜底:进程想干越权事,进不了门。
3.2 IPC/RPC:你以为“函数调用”,其实“是边界检查”
-
鸿蒙生态里跨进程**以 IRemoteObject(Binder风格)**为核心:
- 客户端持有一个远端对象的代理(Proxy),打包参数为二进制消息;
- 服务端是桩(Stub/RemoteObject),在
onRemoteRequest中解包、校验、执行;
-
AccessToken 与 IPC的结合:服务端可通过框架 API 获取调用方 token,逐方法判权,这比“入口判一次、全程通行”更安全。
服务端 RPC 桩(示例,裁剪自常见写法):
import { rpc } from '@kit.IPCKit'
import accessToken from '@ohos.security.accessToken'
const TRANS = { READ_SECRET: 1 }
class SecureStub extends rpc.RemoteObject {
constructor() { super('com.example.secure.IPC') }
onRemoteRequest(code: number, data: rpc.MessageSequence, reply: rpc.MessageSequence, option: rpc.MessageOption): boolean {
try {
// 关键:每个敏感方法前校验
const caller = accessToken.getCallingTokenID()
if (accessToken.verifyAccessToken(caller, 'ohos.permission.SECRET_READ')
!== accessToken.GrantStatus.PERMISSION_GRANTED) {
reply.writeInt(-1) // 自定义错误码:无权限
return true
}
switch (code) {
case TRANS.READ_SECRET: {
const key = data.readString()
const val = secretStore.get(key) ?? ''
reply.writeInt(0)
reply.writeString(val)
return true
}
default:
return super.onRemoteRequest(code, data, reply, option)
}
} catch (e) {
reply.writeInt(-2) // 其他异常
return true
}
}
}
小心 2 件事:
- 不要在 onRemoteRequest 里做重 IO(把业务丢线程池);
- MessageSequence 要及时回收/释放,长期连接避免内存抖动。
3.3 进程通信中的“人设核验”
- 谁在叫我:
getCallingTokenID()拿调用方令牌; - 能做什么:
verify/check指定权限; - 做完要记账:对敏感调用做好埋点与审计(方法、参数指纹、调用来源、结果)。
4. 三者联动到一个“真实场景”
场景:文件同步服务
SyncService,对外提供“导出私有文件到公共媒体库”的方法。
风险点:别的应用若能随便调你的导出,就能“越权读你的私有数据”。
我们怎么做?
- 沙箱保证:私有目录默认外部不可读;
- 权限门槛:导出到媒体库需要
WRITE_MEDIA; - 进程隔离 + 令牌核验:
SyncService的 RPC 每次处理先验对方有WRITE_MEDIA,再做导出; - SELinux兜底:即使你程序写错路径、越权写系统区,策略也会挡住。
导出方法(伪代码):
function exportPrivateToGallery(srcPrivPath: string, dstName: string) {
assertCallerHas('ohos.permission.WRITE_MEDIA') // 刚才封装过的二次校验
// 1) 从私有区读(本进程有权限)
const data = fs.readFile(srcPrivPath)
// 2) 走 MediaLibrary 创建公共媒体文件并写入
const uri = mediaLib.createAsset(/* ... */)
mediaLib.write(uri, data)
return uri
}
5. 攻防小抄:把“安全工程”落到日常开发里
5.1 最小特权与权限分层
- 把“读”和“写”拆成不同权限(如只读查询与危险写操作分开);
- 面向三方插件/模块,再封一层 Facade,对外只暴露“必要最少”的方法。
5.2 UI/后台与多进程
- 背景服务(ServiceExtensionAbility)做权限再核验与速率限制;
- 若要多进程,按域切:与系统服务打交道的那一侧用更严的进程域、策略与审计。
5.3 数据落地与隐私
- 私有文件默认就好,只有在用户明确动作下才导出到公共目录;
- 敏感数据加密存储(Keystore/密钥分级);
- 日志里避免打印敏感字段,必要时做脱敏。
5.4 远程与分布式
- 分布式同步/远程调用,除了常规 Token 校验,再加会话级签名或一次性随机值防重放;
- 关键状态放到受控KV/系统服务,不要自己“裸露端口”。
6. 常见误区(踩坑实录)
- “我在前端 Button 里判断过权限了” → 真正的门在服务端;别忘RPC 入口二次校验。
- “同一台设备,我直接读另一个App的文件吧” → 不可能。走受控分享或 DataShare 接口。
- “把权限一次性全要了省事” → 审核与合规会找你麻烦,用户也会拒。按需申请。
- “权限拿到了就随便玩” → 看看 SELinux,很多资源仍然不让你碰。
- “RPC 就当本地函数” → 没有超时、没有幂等、没有背压和身份核验……迟早出事故。
- “调系统服务失败=系统不稳定” → 多半是你没给 Token 或没权限,或者被策略拒了。
7. 一页纸 Checklist(上线前自检)
-
module.json5只声明必要权限,并按场景动态申请 - 所有 ServiceExtensionAbility/RPC 入口都做 AccessToken 二次校验
- 私有数据不越界:对外导出统一走 受控接口(MediaLibrary、文档选择、DataShare)
- 敏感方法 埋点与审计(来源、参数指纹、结果)
- 异常路径有兜底:权限被撤、系统资源不可用、超时/失败重试策略
- 性能安全并重:
onRemoteRequest不做重 IO,消息对象及时回收 - 开发/测试环境开启 严格策略与告警,预发布打压“侥幸逻辑”
结语:安全是一门“工程学”,不是“玄学”
沙箱机制帮你划好了“地界”,权限管理给了你“钥匙制度”,进程隔离与 AccessToken/SELinux把“守门动作”做到了“每次调用、每份资源、每条路径”。
…
(未完待续)
更多推荐




所有评论(0)