WWDC Quick Look 💓 By SwiftGGTeam
Ready, set, relay: Protect app traffic with network relays

Ready, set, relay: Protect app traffic with network relays

观看原视频

Highlight

Apple 在 iOS 17 和 macOS Sonoma 中提供了一组新的网络中继(Network Relay)API,开发者可以用 MASQUE 和 Oblivious HTTP 协议为 App 的网络流量添加隐私保护层,企业管理员可以用它替代传统 VPN 实现员工对内部资源的安全访问。


核心内容

问题:为什么 VPN 不是所有场景的最佳答案

对于需要保护网络流量的开发者来说,传统方案很有限。要么什么都不做,让用户的 IP 地址暴露给服务器;要么接入 VPN,但 VPN 有几个固有问题:

  1. 范围太大:VPN 通常接管整台设备的所有流量,App 无法只为自己关心的请求开启保护。
  2. 用户体验差:VPN 连接慢、切换网络时需要重新连接、用户需要手动开关。
  3. 信任模型不理想:VPN 服务商能看到用户的真实 IP 和目标访问地址,相当于把隐私交给了一个中间人。

对于企业场景,VPN 配置和分发对 IT 团队来说也是个负担。员工需要安装 VPN 客户端、输入配置、处理掉线重连。

Apple 的方案:网络中继

Apple 在 iOS 17 / macOS Sonoma 中引入了网络中继(Network Relay)的支持。中继是一种特殊类型的代理服务器,基于 IETF 标准化的两个协议:

  • MASQUE:支持代理任意 TCP 和 UDP 连接,不需要修改后端服务器。
  • Oblivious HTTP:专为 HTTP 流量设计,适合 REST API 场景。

中继的核心优势是可以链接(chain)。你可以配置两个中继服务器:第一个中继知道用户的 IP 但不知道访问内容(内容已加密),第二个中继知道访问内容但不知道用户 IP。没有任何单一方能同时获得两者。这正是 iCloud Private Relay 背后的技术。

对开发者来说,这意味着你可以为自己的 App 单独配置中继,不影响设备上的其他流量。对企业来说,中继比 VPN 更轻量——连接更快、只代理指定域名的流量、可以和现有身份提供商集成。


详细内容

在 App 中配置单个中继

配置一个中继只需要几行代码。在 Network framework 中创建一个 ProxyConfiguration 对象即可(04:52):

import Network

let relayEndpoint = NWEndpoint.url(URL(string: "https://relay.example.com")!)
let relayServer = ProxyConfiguration.RelayHop(http3RelayEndpoint: relayEndpoint)

let relayConfig = ProxyConfiguration(relayHops: [relayServer])

关键点:

  • NWEndpoint.url 接受中继服务器的 URL,协议必须是 HTTPS
  • RelayHop 代表中继链路中的一跳,http3RelayEndpoint 指定使用 HTTP/3 协议与中继通信
  • ProxyConfigurationrelayHops 是数组,支持单跳或多跳(chain)
  • 上述代码只创建了配置对象,还没有应用到任何连接上

三种使用方式:Network framework、URLSession、WebKit

中继配置可以注入到 Apple 的三个主要网络 API 中。

方式一:Network framework05:40

import Network

let relayEndpoint = NWEndpoint.url(URL(string: "https://relay.example.com")!)
let relayServer = ProxyConfiguration.RelayHop(http3RelayEndpoint: relayEndpoint)

let relayConfig = ProxyConfiguration(relayHops: [relayServer])

var context = NWParameters.PrivacyContext(description: "my relay")
context.proxyConfigurations = [relayConfig]

let parameters = NWParameters.tls
parameters.setPrivacyContext(context)

let connection = NWConnection(host: "www.example.com", port: 443, using: parameters)
connection.start(queue: .main)

关键点:

  • NWParameters.PrivacyContext 是 iOS 17 / macOS Sonoma 新增的类型,用来封装隐私配置
  • proxyConfigurations 接受一个 ProxyConfiguration 数组
  • setPrivacyContext 将隐私上下文绑定到连接参数上
  • 适用于需要底层网络控制的场景,比如自定义协议或非 HTTP 流量

方式二:URLSession06:07

import Network

let relayEndpoint = NWEndpoint.url(URL(string: "https://relay.example.com")!)
let relayServer = ProxyConfiguration.RelayHop(http3RelayEndpoint: relayEndpoint)

let relayConfig = ProxyConfiguration(relayHops: [relayServer])

let config = URLSessionConfiguration.default
config.proxyConfigurations = [relayConfig]

let mySession = URLSession(configuration: config)
let url = URL(string: "https://www.example.com/api/v1/employees")!
let (data, response) = try await mySession.data(from: url)

关键点:

  • URLSessionConfiguration 新增了 proxyConfigurations 属性
  • 配置后,该 URLSession 的所有请求都通过中继路由
  • 使用 Swift concurrency 的 async/await API 获取数据
  • 服务器只能看到中继的 IP 地址,无法获取用户的真实 IP

方式三:WebKit06:30

import Network

let relayEndpoint = NWEndpoint.url(URL(string: "https://relay.example.com")!)
let relayServer = ProxyConfiguration.RelayHop(http3RelayEndpoint: relayEndpoint)

let relayConfig = ProxyConfiguration(relayHops: [relayServer])

let webkitConfig = WKWebViewConfiguration()
webkitConfig.websiteDataStore = WKWebsiteDataStore.nonPersistent()
webkitConfig.websiteDataStore.proxyConfigurations = [relayConfig]
let webView = WKWebView(frame: .zero, configuration: webkitConfig)

let url = URL(string: "https://www.example.com/api/v1/employees")!
webView.load(URLRequest(url: url))

关键点:

  • WKWebsiteDataStore 新增了 proxyConfigurations 属性
  • 使用了 nonPersistent() 数据存储,这意味着不保留 cookie 和缓存,增强隐私
  • WebView 中的所有网络请求都会通过中继
  • 适用于内嵌浏览器场景,比如 OAuth 登录流程

企业场景:通过配置描述文件部署中继

企业不需要让员工手动配置。管理员可以通过 MDM(移动设备管理)下发配置描述文件(09:15):

<dict>
    <key>PayloadType</key>
    <string>com.apple.relay.managed</string>
    <key>Relays</key>
    <array>
        <dict>
            <key>HTTP3RelayURL</key>
            <string>https://relay.example.com</string>
            <key>PayloadCertificateUUID</key>
            <string>5AB702EC-32F3-48A9-94FE-8EA1C67ACF46</string>
        </dict>
    </array>
    <key>MatchDomains</key>
    <array>
        <string>internal.example.com</string>
    </array>
</dict>

关键点:

  • PayloadTypecom.apple.relay.managed,这是新增的配置描述文件类型
  • HTTP3RelayURL 指定中继服务器地址
  • PayloadCertificateUUID 引用同一描述文件中定义的证书,用于 TLS 验证
  • MatchDomains 限定中继只代理指定域名的流量,其他流量不受影响
  • 这是 VPN 的重大改进:只代理需要的流量,而不是全部

企业场景:通过 NetworkExtension 编程配置

如果企业有自己的管理 App,也可以通过 NetworkExtension 框架编程配置中继(09:42):

import NetworkExtension

let newRelay = NERelay()
let relayURL = URL(string: "https://relay.example.com:443/")
newRelay.http3RelayURL = relayURL
newRelay.http2RelayURL = relayURL

newRelay.additionalHTTPHeaderFields = ["Authorization" : "PrivateToken=123"]

let manager = NERelayManager.shared()
manager.relays = [newRelay]
manager.matchDomains = ["internal.example.com"]

manager.isEnabled = true
do {
    try await manager.saveToPreferences()
} catch let saveError {
    // Handle error
}

关键点:

  • NERelay 是 NetworkExtension 框架新增的类,表示一个中继配置
  • 同时支持 HTTP/3 和 HTTP/2 协议,作为 fallback
  • additionalHTTPHeaderFields 可以添加自定义 HTTP 头,用于身份验证(如 Authorization token)
  • NERelayManager.shared() 是单例,管理系统级的中继配置
  • matchDomains 限定中继的应用范围,只对指定域名生效
  • saveToPreferences() 将配置持久化到系统,需要用户授权

核心启发

  1. 为健康/金融类 App 添加网络隐私保护:如果你的 App 处理敏感数据(健康记录、财务信息、位置数据),通过 URLSessionConfiguration.proxyConfigurations 配置中继,让服务器无法获取用户真实 IP。为什么值得做:隐私法规(GDPR、CCPA)对用户数据的收集和处理有严格要求,隐藏 IP 是降低合规风险的有效手段。怎么开始:选择一个支持 MASQUE 协议的中继服务商,在 URLSessionConfiguration 中设置 proxyConfigurations,然后验证服务器日志中是否只看到中继 IP。

  2. 用中继替代企业 VPN 访问内部 API:企业 App 需要访问内网资源时,不再需要让员工安装 VPN 客户端。通过 MDM 下发 com.apple.relay.managed 配置描述文件,中继只代理 MatchDomains 中指定的域名。为什么值得做:VPN 连接慢、掉线需要手动重连、干扰非企业流量——这些痛点中继都不存在。怎么开始:搭建支持 MASQUE 协议的中继服务器,创建配置描述文件,通过 MDM 推送到设备,然后通过 NERelayManager 或 URLSession 使用。

  3. 使用双中继(two-hop)架构实现更强的隐私保护:在 relayHops 数组中配置两个 RelayHop,第一个中继知道用户 IP 但看不到内容,第二个中继能看到内容但不知道用户 IP。为什么值得做:单中继方案中,中继提供商同时知道你是谁和你在访问什么。双中继架构确保没有任何单一实体能关联两者。怎么开始:选择两个不同的中继提供商,创建两个 RelayHop 对象,放入 ProxyConfiguration(relayHops: [hop1, hop2])

  4. 为 WebView 内容添加网络隔离:如果你的 App 内嵌了 WebView 展示第三方内容,通过 WKWebsiteDataStore.nonPersistent() 配合 proxyConfigurations,让 WebView 的网络请求通过中继路由且不保留本地数据。为什么值得做:内嵌浏览器是隐私泄露的重灾区——第三方脚本、追踪器都可以通过 WebView 获取用户信息。怎么开始:创建 WKWebViewConfiguration,设置 websiteDataStorenonPersistent() 并配置 proxyConfigurations

  5. 用自定义 HTTP 头实现无密码认证:通过 NERelay.additionalHTTPHeaderFields 添加 Authorization 头,中继服务器可以验证请求来源而不需要用户输入密码。为什么值得做:企业中继访问内部资源时,传统 VPN 需要额外的用户名密码认证步骤。自定义 HTTP 头可以与 SSO 系统集成,实现无感知认证。怎么开始:在企业 IdP 中生成 API token,通过 additionalHTTPHeaderFields 传入,中继服务器端验证 token。


关联 Session

评论

GitHub Issues · utterances