我是兰瓶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)通常要在处理请求前主动校验对方令牌

    1. 拿到调用者的 TokenID
    2. verify/check 方法校验某权限;
    3. 决定是否继续执行。

服务端校验示例(伪代码,按 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 件事

  1. 不要在 onRemoteRequest 里做重 IO(把业务丢线程池);
  2. MessageSequence 要及时回收/释放,长期连接避免内存抖动。

3.3 进程通信中的“人设核验”

  • 谁在叫我getCallingTokenID() 拿调用方令牌;
  • 能做什么verify/check 指定权限;
  • 做完要记账:对敏感调用做好埋点与审计(方法、参数指纹、调用来源、结果)。

4. 三者联动到一个“真实场景”

场景:文件同步服务 SyncService,对外提供“导出私有文件到公共媒体库”的方法。
风险点:别的应用若能随便调你的导出,就能“越权读你的私有数据”。

我们怎么做?

  1. 沙箱保证:私有目录默认外部不可读;
  2. 权限门槛:导出到媒体库需要 WRITE_MEDIA
  3. 进程隔离 + 令牌核验SyncService 的 RPC 每次处理先验对方有 WRITE_MEDIA,再做导出;
  4. 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. 常见误区(踩坑实录)

  1. “我在前端 Button 里判断过权限了” → 真正的门在服务端;别忘RPC 入口二次校验
  2. “同一台设备,我直接读另一个App的文件吧”不可能。走受控分享或 DataShare 接口。
  3. “把权限一次性全要了省事” → 审核与合规会找你麻烦,用户也会拒。按需申请
  4. “权限拿到了就随便玩” → 看看 SELinux,很多资源仍然不让你碰
  5. “RPC 就当本地函数” → 没有超时、没有幂等、没有背压和身份核验……迟早出事故
  6. “调系统服务失败=系统不稳定” → 多半是你没给 Token 或没权限,或者被策略拒了。

7. 一页纸 Checklist(上线前自检)

  • module.json5 只声明必要权限,并按场景动态申请
  • 所有 ServiceExtensionAbility/RPC 入口都做 AccessToken 二次校验
  • 私有数据不越界:对外导出统一走 受控接口(MediaLibrary、文档选择、DataShare)
  • 敏感方法 埋点与审计(来源、参数指纹、结果)
  • 异常路径有兜底:权限被撤、系统资源不可用、超时/失败重试策略
  • 性能安全并重:onRemoteRequest 不做重 IO,消息对象及时回收
  • 开发/测试环境开启 严格策略与告警,预发布打压“侥幸逻辑”

结语:安全是一门“工程学”,不是“玄学”

沙箱机制帮你划好了“地界”,权限管理给了你“钥匙制度”,进程隔离AccessToken/SELinux把“守门动作”做到了“每次调用、每份资源、每条路径”。

(未完待续)

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐