DEBUG.WIKI
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
雨云;给项目一台云服务器
RackNerd;$21.99/年 · 1GB KVM

URLSession 返回 -1022:排查 ATS 拦截并缩小 HTTP 例外范围

URLSession 返回 -1022 时,先检查实际失败 URL、HTTPS 与证书配置;确需访问旧 HTTP 服务时,只为必要域名配置 ATS 例外,并核对最终应用包。

URLSession 请求尚未拿到 HTTP 响应,就返回 NSURLErrorDomain Code=-1022,错误说明包含 App Transport Security policy requires the use of a secure connection。这表示请求受到 App Transport Security(ATS)的安全策略限制。增加超时、修改 JSON 解析或重新登录,通常不会改变这项判断。

本文针对使用系统 URL Loading System 的 iOS 应用。ATS 默认作用于链接 iOS 9 SDK 或更新 SDK 的应用;它与服务端返回的 401、403、500 属于不同层次。请求使用 HTTPS,也仍需满足证书信任和 TLS 等要求。

记录实际失败的地址

先在调试环境核对错误域、错误码和失败地址。下面的示例只记录协议、主机和路径,不输出查询参数、Cookie 或响应正文;不要把带令牌的完整 URL 长期写入日志。

import Foundation

func describeNetworkError(_ error: Error) {
    let nsError = error as NSError
    print("domain:", nsError.domain, "code:", nsError.code)
    if let url = nsError.userInfo[NSURLErrorFailingURLErrorKey] as? URL {
        print("scheme:", url.scheme ?? "",
              "host:", url.host ?? "",
              "path:", url.path)
    }
}

检查应用的环境配置是否仍指向 http://,以及接口是否从 HTTPS 重定向到 HTTP。原始入口正确,不代表后续每一跳都正确;域名例外也不会自动覆盖跳转后的另一个主机。路径本身可能含用户标识,处理敏感业务时还应删去或脱敏。

若错误变成 -1202 等证书错误,应沿证书信任问题继续排查,不能仍把它当成同一个 -1022。浏览器能打开一个地址,也不足以证明应用的 ATS 检查会通过。

服务端支持 HTTPS 时,直接修复接口配置

先确认该服务真的提供 HTTPS,并且证书匹配请求域名、没有过期、信任链可被设备接受。随后把应用的接口基址改为服务提供的正式 HTTPS 地址,而不是对任意字符串批量替换 http。

对于自有服务,还要确认负载均衡、反向代理和后端生成的跳转地址都保留 HTTPS。只把入口改为 HTTPS,后端仍返回 HTTP 的登录跳转或文件地址,可能继续触发拦截。

可以在 Mac 上对待诊断的服务器运行:

/usr/bin/nscurl --ats-diagnostics --verbose https://api.example.com

api.example.com 是占位域名,应替换为实际接口主机。这条命令会发起网络诊断请求,并比较默认 ATS 与多种例外组合。重点看 Default ATS Secure Connection 的结果;某个放宽组合显示 PASS,只能帮助定位缺少的安全条件,不表示应该照抄该组合到应用。

如果连允许任意加载的诊断组合都失败,应继续看错误细节。证书主机名不匹配等基础信任失败,并不会因为关闭 ATS 的附加检查自动消失。Mac 上的结果也需要在实际 iOS 目标设备、网络和接口路径上复核。

无法升级的 HTTP 服务,只给必要主机配置例外

只有确实需要连接无法升级的 HTTP 服务时,再考虑域名例外。下面是需要合并到应用 Info.plist 根字典中的完整条目示例;已有 NSAppTransportSecurity 时,应合并其中内容,不要创建第二个同名键。

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSExceptionDomains</key>
    <dict>
        <key>legacy-api.example.com</key>
        <dict>
            <key>NSExceptionAllowsInsecureHTTPLoads</key>
            <true/>
        </dict>
    </dict>
</dict>

这会允许示例主机的 HTTP 加载。主机名必须换成实际请求中的主机,使用小写;不要包含 http://、端口号或路径,也不要使用 *.example.com。确实需要覆盖该域下所有子域时,才添加 NSIncludesSubdomains 并设为 Boolean true。只需要一个 API 子域时,直接列出这个子域更容易控制范围。

当前文档允许 iOS 17 起在例外字典中使用 IP 地址和 CIDR 范围;这与早期系统的处理规则有差异。如果应用仍支持旧系统,不要把域名例外、IP 地址请求与本地网络规则混为一谈,应针对最低支持版本核对。本文示例仅处理具名域名,不涉及本地网络权限。

HTTP 流量仍然没有 TLS 的机密性和完整性保护,不适合承载登录凭据、会话令牌或个人信息。该例外不会单独让 HTTPS 的无效证书变得可信,也不会自动放宽 TLS 版本和前向保密要求。设置这项例外需要在 App Store 审核时提供理由,应保留必须使用它的业务条件,并安排可移除它的服务端改造。

加了配置还失败,检查实际应用包

确认修改的是运行应用的 target,而不是单元测试或 UI 测试 target。使用生成 Info.plist 的工程,应在应用 target 的 Info 配置中添加条目;使用显式文件的工程,则检查该 target 的构建设置实际引用哪份文件。

构建后可在 Xcode 的 Products 中定位应用,查看包内最终生成的 Info.plist。若那里没有例外,继续修改源码旁边某份没有参与构建的 plist 不会生效。还要确认 Boolean 没有误写为字符串,且字典层级与示例一致。

不要把 NSAllowsArbitraryLoads = true 当成默认修复。它会扩大放宽范围,也可能与其他 ATS 配置产生优先级关系。NSAllowsArbitraryLoadsInWebContent 针对 Web 内容,不是普通 URLSession 请求的通用开关。

验收时,确认预期接口返回正常业务响应;如果使用了例外,还要确认它只覆盖必需主机。改回 HTTPS 后删除不再需要的例外,再重新构建和验证请求。请求恢复、配置范围合理、旧 HTTP 地址与不安全跳转清理完成,才算结束这次修复。