Highlight
ManagedApp 框架在 iOS/iPadOS 18.4 与 visionOS 2.4 引入,允许 MDM 直接向托管 app 投递配置、密码、证书和支持硬件绑定密钥的 ACME 身份,并通过 async sequence 在变更时实时通知 app。
核心内容
企业里第一次启动一个新装的 app,往往是体验最糟糕的一刻。员工要输服务器地址、用户名、密码、二次验证码,然后进设置页一项项勾选,对齐组织的合规要求。Bob Whiteman 在演讲开场就把这个痛点摆了出来(00:51):步骤一多就容易出错,员工放弃使用,IT 接到工单,组织最后干脆把这个 app 从清单里删掉。开发者想自己解决,意味着要做身份联邦、搭配置网站、对接证书颁发机构、维护组织定制版本——全是和核心功能无关的活。
ManagedApp 框架的思路是把这些活全部交给系统。管理员在 MDM 端配置好,app 一启动就拿到一份现成的配置和密钥,不再需要任何首次设置流程(02:25)。框架统一承担四类数据的安全分发:app 自定义配置、密码、证书、身份(支持 PKCS #12、SCEP 和 ACME,后者还能绑定硬件密钥并做远程证明)。整套机制要求 MDM 使用 declarative device management,从 app 被管理那一刻开始生效,覆盖所有 MDM 注册类型。
最关键的设计是配置 schema 由 app 开发者自己定义,而不是 Apple 或 MDM 厂商定一套通用格式。Bob 反复强调这一点(06:44):你比任何人都懂自己的 app,只有你能给出最合适的可配置面。框架不是方案,是构建方案的平台。
详细内容
ManagedApp 框架由四个独立的 provider 组成,分别对应密码、证书、身份、自定义配置(12:20)。前三个返回系统内建类型,自定义配置则由 app 决定数据结构。
第一步是把自定义配置的 schema 写成一个 Decodable 类型。Landmarks 示例里只有一个字段 collection:
// Your app's managed configuration
struct LandmarksManagedConfig: Decodable {
private(set) var collection: LandmarkCollection?
private enum CodingKeys: String, CodingKey {
case collection
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
collection = try values.decode(LandmarkCollection.self, forKey: .collection)
}
}
关键点:
LandmarksManagedConfig实现Decodable,框架会用它把 MDM 投递的字典反序列化成 Swift 类型。collection用private(set)暴露只读视图,配置只能由框架更新。CodingKeys显式声明键名,避免依赖属性名隐式匹配。init(from:)里用decode而不是decodeIfPresent,意味着缺字段就抛错——schema 不匹配时让 app 早失败。
第二步是把配置注入 app 的运行时。ManagedAppConfigurationProvider 返回的是一个 AsyncSequence,配置变更时循环体会再次进入(0:02):
// Receiving the current configuration
import ManagedApp
// [...]
var managedCollection: LandmarkCollection?
// [...]
func loadCollections() {
// [...]
Task {
let configProvider = ManagedAppConfigurationProvider()
for await config in await configProvider.configurations(LandmarksManagedConfig.self) {
// config's type is LandmarksManagedConfig?
managedCollection = config?.collection
} // Loops forever
}
}
关键点:
import ManagedApp引入框架,模块名就叫这个。configurations(_:)接受 schema 类型,返回的元素是LandmarksManagedConfig?,没有配置或 app 处于非托管状态时为nil。for await循环永不结束,MDM 每次推新配置都会触发一次迭代,省掉手写 KVO 或通知中心。- 把
Task放在数据加载逻辑里,让配置生效和数据加载在同一个生命周期。
第三步处理身份认证。VPN 扩展和企业服务调用经常需要客户端证书,框架把 SecIdentity 直接喂给 URLSession 委托:
// Using an identity
final class MyURLSessionDelegate: NSObject, URLSessionDelegate {
func urlSession(_ session: URLSession,
didReceive challenge: URLAuthenticationChallenge)
async -> (URLSession.AuthChallengeDisposition, URLCredential?) {
switch challenge.protectionSpace.authenticationMethod {
case NSURLAuthenticationMethodClientCertificate:
// Look up the identity
let provider = ManagedAppIdentitiesProvider()
let id = "AssetDownloadClient"
guard let identity = try? await provider.identity(withIdentifier: id) else {
// No identity, cancel the challenge
return (.cancelAuthenticationChallenge, nil)
}
// Use the identity to authenticate.
return (.useCredential, URLCredential(identity: identity,
certificates: nil,
persistence: .forSession))
default:
return (.performDefaultHandling, nil)
}
}
}
关键点:
URLSessionDelegate的 async 版本在 challenge 到来时挂起,等身份取回来再回调。ManagedAppIdentitiesProvider().identity(withIdentifier:)按 schema 里约定的标识符取身份,标识符由 app 自己定义。- 取不到身份就调用
cancelAuthenticationChallenge,让请求直接失败而不是回退到弱认证。 URLCredential的persistence用.forSession,私钥不会被复制到 Keychain 之外。
许可证场景是这个框架最直观的升级。Bob 在 09:00 对比了旧方案:分发字符串 token 会面临泄漏风险,而 ManagedApp 框架可以分发由组织 CA 签发的加密身份,私钥在设备上生成,永远不出设备。VPN 扩展走得更远,可以拿到带硬件证明的 ACME 身份(10:46),从根上挡住中间人接管 VPN 凭据的攻击路径。
核心启发
-
把旧的 ManagedAppConfiguration 迁到 ManagedApp 框架:为什么值得做——旧方案不支持 app 扩展、不支持证书和身份,且没有变更通知;新框架在 iOS/iPadOS 18.4 之后是更完整的替代品(09:46)。怎么开始——先定义一个最小 schema,跑通”MDM 推送 → app 收到”的链路,再把旧
UserDefaults读取逻辑逐项搬过去,每搬一项就删掉旧代码。 -
给 VPN 扩展接入硬件绑定的 ACME 身份:为什么值得做——VPN 是攻击面最高的入口之一,把私钥钉在 Secure Enclave 上并附带远程证明,能让组织的 VPN 准入策略真正可信(10:30)。怎么开始——在 schema 里声明一个身份字段,要求 MDM 用 ACME 颁发;在
NEVPNManager的认证回调里复用上面那段ManagedAppIdentitiesProvider的代码。 -
用许可证身份替换许可证 token:为什么值得做——token 一旦泄漏组织就要继续付费,而设备生成的密钥对永远不离开设备,泄漏面接近零(08:30)。怎么开始——服务端先支持 mTLS,发证流程接组织 CA;客户端用
ManagedAppIdentitiesProvider拿身份做客户端证书认证。 -
为 SSO 扩展接收一次性密码和初始凭据:为什么值得做——身份提供商通常自己设计认证协议,过去只能让用户手输凭据;现在可以让管理员通过 MDM 直接预置初始密码或绑定令牌(11:42)。怎么开始——在 SSO 扩展里加
ManagedAppPasswordsProvider,启动时尝试读取,存在则跳过手动输入流程。 -
配置变更走 async sequence 做整体重载:为什么值得做——
for await循环天然处理多次更新,避免逐字段监听导致的中间不一致状态(06:00)。怎么开始——在Task内迭代configurations(_:),每次收到新值就走一遍完整的”应用配置”流程,而不是 diff 单个字段。
关联 Session
- What’s new in Apple device management and identity — 设备管理与身份新特性的总览,配合 ManagedApp 框架看部署侧的全貌。
- Discover the Enterprise APIs for iOS — 企业 API 的扩展能力,和 ManagedApp 框架在权限模型上互补。
- What’s new in privacy — 隐私边界的变化,理解托管配置如何在隐私框架下安全分发。
- Streamline sign-in with passkey upgrades and credential managers — 认证侧的演进,与 ManagedApp 提供的身份能力组合使用。
评论
GitHub Issues · utterances