大多数浏览器和
Developer App 均支持流媒体播放。
-
简化本地授权流程
了解如何借助 LocalAuthentication 中最新推出的授权 API,以保护用户数据的隐私和安全。我们将介绍 LocalAuthentication 如何对您 App 中的密码、密钥和其他敏感资源授权访问,同时降低对常见本地授权方法 (如触控 ID 和面容 ID) 的安全性和可用性的依赖和复杂程度。
资源
-
下载
♪ ♪
Felix Acero: 大家好 我叫 Felix Acero 是一名软件工程师 供职于安全工程与架构团队 在本视频中 我将向您展示如何使用 LocalAuthentication 框架 来改进 App 身份验证和授权流 我们将首先了解身份验证(Authentication) 和授权(Authorization)的一般概念 以及它们如何应用于您的 App 然后我们将回顾 现有的 LocalAuthentication API 尤其是 LAContext 如何帮助您实现广泛的授权方案 最后我们将看到 今年添加到 LocalAuthentication 的新 API 如何帮助您进一步简化授权代码
那么我们先来说说身份验证和授权 身份验证和授权是不同 但密切相关的安全概念 一方面 身份验证 是验证用户身份的行为 另一方面 授权是验证是否允许给定用户 对具体资源执行特定操作的行为 综上所述 我们可以看到 由于我们首先需要验证 用户是否就是 他们所说的那个人 然后才能 评估他们可以使用哪些资源和操作 因此我们可以说 身份验证实际上启用了授权 为了帮助说明这些概念 我们来看一个具体示例 其中涉及由您的 App 管理的公共安全资源 例如 Secure Enclave 密钥
Secure Enclave 密钥 是一种特殊类型的非对称密钥 它绑定到特定设备 并受到 与主处理器隔离的 基于硬件的密钥管理器的保护 这些密钥的特别之处在于 当您在 Secure Enclave 中存储密钥时 您实际上从未处理过该密钥 而是指示 Secure Enclave 使用它执行操作 Secure Enclave 密钥 可以与访问控制列表 或 ACL 相关联 访问控制列表指定了 执行特定操作所需满足的要求 如对 blob 进行签名或解密
它们可以指定给定项何时可用 例如在设备解锁后 以及允许执行 某些操作所需的身份验证要求 在本例中 假设您的 App 希望通过生物认证 来保护其密钥的签名 和解密操作 同时确保密钥 仅在设备解锁后可用 现在让我们看看涉及此密钥的 签名操作的授权流是什么样子的
首先 App 发出 使用密钥对 blob 进行签名的请求
然后在验证 App 是否可以访问密钥后 系统继续识别签名操作的 授权要求 在这种情况下 签名操作需要当前注册用户 进行成功的生物认证 然后系统将通过标准 UI 引导用户完成生物认证 身份验证成功后 系统将验证 是否已满足所有剩余的授权要求 然后最终执行签名操作 并将签名的 blob 返回给您的 App
我们来分解一下 这个流程中涉及的主要组件 看看它们如何适应我们最初的定义 首先 我们有一个资源 Secure Enclave 密钥 其次 我们有一个 可以用密钥执行的操作 第三 我们有一组要求 其中包括 指定允许执行操作的人员 以及应用于验证其身份的 身份验证方法 将此示例的参数插入到我们的定义中 我们可以看到 对于身份验证来说 是否是正确用户的问题 是通过生物认证来回答的 而对于授权 是否允许用户 使用私钥进行签名操作的问题 是通过验证访问控制列表中 指定的要求来回答的 现在我们已经在较高的层级上 了解了它是如何工作的 我们可以看看如何使用当前的 LocalAuthentication API 来实现这样的流程 让我们首先快速回顾一下 LAContext 提供的功能 它是框架的核心组件之一 LAContext 可用于评估用户的身份 它在需要生物特征或密码验证时 处理用户交互 它还与 Secure Enclave 连接 以实现对生物特征数据的安全管理 从这个角度来看 LAContext 可用于 支持您的身份验证用例 LAContext 也可以与其他框架 结合使用以支持授权流 例如 您可以用它 来评估 AccessControl 列表 就像我们在前面的例子中看到的那样 我们来仔细看看 我们需要做的第一件事是 访问与我们的私钥 相关联的 ACL 我们可以在 Security 框架提供的 SecItemCopyMatching API 的 帮助下做到这一点 确保在查询中 提供 ReturnAttributes 密钥
一旦我们获得 对访问控制列表的访问权 我们就可以直接使用 LAContext 和 evaluateAccessControl API 对其进行评估 这种方法给您带来的最大好处是 它可以让您在 App 中 决定提示用户进行此授权的 正确时机和正确位置 在这种情况下 由于访问 AccessControl 列表 要求对签名操作进行生物认证 LAContext 将显示 熟悉的 FaceID 或 TouchID UI
一旦 ACL 在我们的 LAContext 中得到授权 我们就可以将其作为查询的一部分 来获取对密钥的引用 我们通过将 LAContext 附加到 UseAuthenticationContext 密钥下的 SecItem 查询来实现这一点
通过将 LAContext 绑定到私钥引用 我们可以确保执行签名操作 不会触发另一个身份验证 同时允许操作在没有 不必要提示的情况下继续进行 这些绑定还意味着 在 LAContext 失效之前 之后的签名不需要额外的用户交互
LAContext 提供了很大的灵活性 它允许您控制授权流中 涉及的每个步骤和参数 它可以与其他框架 如 Security 框架 结合使用 这反过来又解锁了广泛的用例 然而这种多功能性 是以更高的代码复杂性为代价的 需要您仔细编排几个框架提供的 API 根据您的使用情况 LAContext 可能是适合您的工具 尤其是当您的 App 的主要价值 主张要求对密钥 秘密 上下文和访问控制列表 进行底层访问时 然而 如果您的 App 需要的 只是授权访问内容或敏感资源的方式 那么您可能想用一个更简单的 API 来权衡这种灵活性 这就把我们带到了最后一个话题 简化您的 App 作为 iOS 16 和 macOS 13 的新功能 LocalAuthentication 引入了 更高层级的 以授权为中心的 API 新的 API 建立在 LocalAuthentication 中的现有概念 例如 LAContext 之上 旨在简化常见 授权流的实施 让您可以将所有精力集中在 您的 App 的核心价值主张上 新 API 引入的最重要的 抽象类是 LARight
您可以给 LARight 提供的 最简单用例 是帮助您对 App 定义的 资源授权操作 例如 您可以使用权限来帮助您 访问 App 的用户配置文件部分 首先 需要您的用户 进行成功的生物认证
默认情况下 权限受到 一组身份验证要求的保护 允许用户使用 Touch ID FaceID Apple Watch 或它们的设备密码进行身份验证 这取决于它们使用的设备
您还可以选择将您的权限 与更细粒度的需求相关联 这允许您进一步限制身份验证的方法 让我们看看如何 在代码中使用 LARights
我们需要做的第一件事是 实例化我们的权利 我们通过指定它的需求来做到这一点 在这种情况下 我们的登录权限 将要求用户 使用生物特征 或提供设备密码进行身份验证 然后我们继续验证当前用户 是否可以获得登录权限 我们使用这些信息来确定 我们是否可以继续登录操作 或者我们是否需要将用户 重定向到我们 App 的公共部分 最后 我们可以继续实际的授权操作 提供一个本地化的 用户可以在授权 UI 中 看到的原因
当以这种方式授权权利时 将显示一个全新的 系统驱动的 UI UI 在 App 窗口中呈现 并为用户提供相关信息 帮助他们了解操作的来源和目的 我们相信 新的外观将允许您精心设计 与 App 无缝集成的授权流 并为用户提供更多上下文和信息
现在我们已经了解了 如何创建和授权一个权利 让我们进一步研究它的生命周期 权利以未知状态开始其生命周期 一旦您的 App 发出 authorize() 请求 权限的状态就会更改为授权 此时用户将看到我们 在上一张幻灯片中 看到的授权 UI
根据操作的成功或失败 权限可以转换为授权或未授权状态 这是您的 App 最重要的状态转换 最后 权限也可以从授权状态 转移到未授权状态 当您的 App 在右边显式 发出取消授权请求时 或者当右边的实例被释放时 就会发生这种情况
请务必保留对您的权利的强引用 以维护其授权状态
取消权限授权后 您的 App 可以继续 发出授权请求以重新开始该周期 可以查询和观察之前的所有状态转换 如果您有权访问 LARight 实例 则可以直接查询其状态属性 您还可以使用 KVO 或 Combine 来观察所有状态转换 此外 如果您的 App 处理多个权限 那么您可以通过侦听 didBecomeAuthorized 和 didBecomeUnauthorized 通知 从一个位置观察所有权限的状态 这些通知会在检测到授权状态更改后 发布到默认 NotificationCenter
在继续之前 让我们回到我们的示例 添加一个注销操作来取消登录权限 通过这种操作 我们可以保证用户下次登录时 需要新的授权
到目前为止 我们已经了解了 如何使用正确的实例 来授权对 App 定义的资源的操作 我们还了解了这些权限的 生命周期和状态 最终是如何与运行时联系在一起的 这意味着在 App 的每个会话上 您都需要正确地 实例化和配置这些权限 那么让我们看看如何保持权限 以及这为您的 App 带来了什么样的可能性
LARights 可以在 权利存储的帮助下持久化
持久化后 权限由唯一的 Secure Enclave 密钥支持 该密钥受访问控制列表或 ACL 保护 该列表或 ACL 与权限的授权要求相匹配 这种方法帮助我们确保授权需求 在权利被持久化之后仍然是不可变的
您还可以访问支持您权限的私钥 并使用它执行受保护的加密操作 如解密 签名和密钥交换
相应的公钥也是可访问的 可以用于执行 加密和签名验证等操作 因为这是一个公钥 所以您还可以导出与其关联的字节
只有在成功授权权限后 才允许私钥操作 相比之下 总是允许公钥操作
当坚持您的权利时 您也有机会存储一个 单一的 不变的秘密 该秘密还与符合您权限授权要求的 访问控制列表相关联 并且只有在权限授权后 才能访问该秘密
总之 LAPersistedRights 是在 权利存储的帮助下创建的 它们只配置一次 其授权要求是不变的 因为它们是存储的 所以可以在 App 的不同会话中使用 在内部 它们绑定到特定设备 并由唯一的 Secure Enclave 密钥支持 该密钥可用于执行不同的加密操作 具体取决于权限的授权状态 最后 它们可以用来保护一个单一的 不可更改的秘密 该秘密只有在授权后才可用 现在我们已经了解了 PersistedRights 提供的一些功能 让我们看看它们如何帮助我们实现 我们在演示开始时讨论的场景 我们希望在该场景中授权签名操作 我们首先实例化一个常规权限 指定其授权要求 在这种情况下 我们希望确保该权利 仅授予在创建我们的权利时 在设备中具有生物特征注册的用户 因此我们使用 biometryCurrentSet 要求
然后我们可以 在权利存储的帮助下持久化权限 提供唯一标识符 下次我们需要在 App 的 未来会话中获取权限时 这个标识符将非常有用
一旦权限被持久化 我们就可以立即访问它的公钥 并开始用它执行不受保护的操作 而不需要显式授权 在这个例子中 我们只是导出它的公共字节
稍后 当需要执行签名操作时 我们可以使用在创建过程中 提供的唯一标识符 从存储中检索我们的权利 然后我们可以通过 我们右边的授权操作 对当前用户进行授权 此时 系统将引导用户 完成身份验证过程 并验证是否满足所有授权要求
在权限被授权后 我们可以使用它的私钥 来执行受保护的加密操作 在这种情况下 我们使用私钥 对 App 后端服务器 发出的质询进行签名 总结一下 我们讨论了身份验证 和授权的通用概念之间存在的关系 特别是身份验证如何启用授权 我们讨论了 LAContext 提供的一些特性 以及它如何与 Security 等框架结合 以解锁非常强大和可扩展的授权流程 最后 我们研究了新添加的 LARight 如何帮助您简化代码 以实现某些授权用例 我们邀请您查看 App 中 LocalAuthentication 的现有用法 并考虑我们今天讨论的某些功能 是否可以帮助您简化代码 同时仍然保护用户的隐私和安全 谢谢
-
-
4:58 - LAContext (authorize a signature operation 1)
let query: [String: Any] = [ kSecClass as String: kSecClassKey, kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave, kSecAttrApplicationTag as String: "com.example.app.key", kSecReturnAttributes as String: true, ] var item: CFTypeRef? = nil guard SecItemCopyMatching(query as CFDictionary, &item) == errSecSuccess, let attrs = item as? NSDictionary, let accessControl = attrs[kSecAttrAccessControl] else { throw .aclNotFound }
-
5:15 - LAContext (authorize a signature operation 2)
let context = LAContext() try await context.evaluateAccessControl(accessControl as! SecAccessControl, operation: .useKeySign, localizedReason: "Authentication is required to proceed")
-
5:44 - LAContext (authorize a signature operation 3)
let query: [String: Any] = [ kSecClass as String: kSecClassKey, kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave, kSecAttrApplicationTag as String: "com.example.app.key", kSecReturnRef as String: true, kSecUseAuthenticationContext as String: context ] var item: CFTypeRef? = nil guard SecItemCopyMatching(query as CFDictionary, &item) == errSecSuccess, item != nil else { throw .keyNotFound }
-
6:00 - LAContext (authorize a signature operation 4)
let privateKey = item as! SecKey var error: Unmanaged<CFError>? guard let sgt = SecKeyCreateSignature(privateKey, self.algorithm, blob, &error) as Data? else { throw .signatureFailure }
-
8:28 - LA Right (basic usage)
// LARight: Basic usage func login() async { self.loginRight = LARight(requirement: .biometry(fallback: .devicePasscode)) do { try await loginRight.checkCanAuthorize() } catch { navigateTo(section: .public) return } do { try await self.loginRight.authorize(localizedReason: self.localizedReason) navigateTo(section: .protected) } catch { showError(.authenticationRequired) } }
-
11:01 - LARight (logout and deauthorization)
// LARight: Basic usage func login() async { self.loginRight = LARight(requirement: .biometry(fallback: .devicePasscode)) // ... do { try await self.loginRight.authorize(localizedReason: self.localizedReason) navigateTo(section: .protected) } catch { showError(.authenticationRequired) } } func logout() async { await self.loginRight.deauthorize() }
-
13:44 - LAPersistedRight
// LAPersistedRight: Retrieval and private key usage func generateClientKeys() async throws -> Data { let login2FA = LARight(requirement: .biometryCurrentSet) let persisted2FA = try await LARightStore.shared.saveRight(login2FA, identifier: "2fa") return try await persisted2FA.key.publicKey.bytes } func signChallenge(_ challenge: Data, algorithm: SecKeyAlgorithm) async throws -> Data { let persisted2FA = try await LARightStore.shared.right(forIdentifier: "2fa") let localizedReason = "Biometric authentication is required to proceed" try await persisted2FA.authorize(localizedReason: localizedReason) return try await persisted2FA.key.sign(challenge, algorithm: algorithm) }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。