首页 / 资讯中心 / 文章详情

iOS HTTP请求被ATS拦截怎么办?从原理到配置到迁移一次讲清

iOS HTTP请求被ATS拦截怎么办?从原理到配置到迁移一次讲清 ★ FEATURED ARTICLE
一次搞懂 iOS 的 HTTP 请求拦截问题做 iOS 开发的人几乎都撞过同一堵墙接了个老接口HTTP 的地址真机一跑请求直接失败控制台甩出一句 “App Transport Security has blocked a cleartext HTTP connection” 。很多人第一反应是 “苹果怎么回事为什么不让我发请求”然后开始上网找各种 Info.plist 的改法。这个问题的本质是 iOS 系统里的 ATSApp Transport Security应用传输安全机制在把关。它从 iOS 9 开始就成为默认策略强制要求 App 的所有网络请求走 HTTPS 加密通道。对于刚接触 iOS 开发的朋友来说这往往是最早遇到的“系统级拦路虎”对老项目来说则是从 HTTP 过渡到 HTTPS 时绕不开的历史包袱。这篇文章就直接把这堵墙拆开来看先说清楚 ATS 到底在保护什么再给出开发阶段的临时放行配置接着解释生产环境的正解最后整理几个我实际排查中总结出来的经验。无论你是刚开始学 iOS 的新手还是正在维护老项目这篇文章都能帮你少走弯路。1. 一切从 ATS 说起为什么 iOS 拦着 HTTP 不放1.1 ATS 的底层逻辑ATS 并不是一个“禁止 HTTP”的开关而是一套传输安全策略。它的设计初衷很朴素明文 HTTP 请求在网络上传输时内容可以被中间设备轻易读取甚至篡改。想象一下你在一张公开明信片上写银行卡号邮递员每经手一次都能看到你的卡片信息这就是 HTTP 的处境。HTTPS 在 HTTP 之上加了加密层相当于把明信片换成密封信封中间人只能看到信封看不到内容。苹果从 iOS 9 开始默认开启 ATS要求所有使用 URLSession 等系统网络接口的请求都必须满足以下条件使用 HTTPS 协议服务端 TLS 版本满足要求1.2 及以上证书链校验通过且证书是受信任的 CA 签发或已安装到设备信任列表加密套件满足安全要求。只要有一条不满足系统就直接在底层帮你拦截掉你甚至看不到任何响应数据。这个拦截发生在网络层之下所以didFailWithError里拿到的错误信息往往比较笼统这也是很多人初次排查时摸不着头脑的原因。1.2 开发环境和线上环境的诉求冲突ATS 在安全上是好事但给开发调试带来了实际困难。比如后端同事只起了个本地 HTTP 服务或者测试环境的接口还没来得及上 HTTPS这时候 App 连调试都跑不通更别提业务联调了。苹果也考虑到了这种情况所以在 Info.plist 里预留了若干配置项允许开发者针对特定情况放宽限制。问题是这些配置项的名字相似、层级复杂网上很多教程只告诉你“照着抄”没告诉你每一项到底放开到什么程度。抄错配置的后果轻则 App 审核被拒重则把整个网络安全防线开了个大洞。后面我逐一拆解这些参数。2. 开发调试期的快速放行方案2.1 全局放开 HTTP 请求NSAllowsArbitraryLoads最快见效的办法是在 Info.plist 里加上NSAppTransportSecurity字典然后把NSAllowsArbitraryLoads设为true。keyNSAppTransportSecurity/key dict keyNSAllowsArbitraryLoads/key true/ /dict这段配置的意思是允许所有 HTTP 明文请求系统不再做任何 ATS 拦截。特点就是“快”改完立即生效适合开发环境调试。但我要先泼一盆冷水这个配置只适合本地调试而且最好是在 Debug 包或模拟器验证时临时用绝对不要直接带到上线版本里。原因很简单它相当于把整个 App 传输安全体系彻底关闭用户的请求数据在网络上裸奔审核被拒还是小事真出现数据安全问题责任全在开发者。如果你只是在真机上调试更推荐的做法后面会说用NSAllowsLocalNetworking或者域名白名单既不影响当前调试也不把防线全拆掉。2.2 按域名白名单放开NSExceptionDomains比全局放开更精细的方式是只对特定域名放行 HTTP。在NSAppTransportSecurity里有一个NSExceptionDomains字典键是你需要放行的域名值是对该域名的 ATS 例外配置。keyNSAppTransportSecurity/key dict keyNSExceptionDomains/key dict key192.168.1.100/key dict keyNSExceptionAllowsInsecureHTTPLoads/key true/ /dict /dict /dict这个写法的作用范围只在指定域名内。比如上面这段放行了局域网的某台测试机其他域名的请求依然受 ATS 保护安全性比全局放开高很多。使用白名单方式有一个要注意的细节配置里如果用 IP 地址作为 key某些系统版本下对 IP 的处理可能不完全符合预期。在实际项目里如果调试地址是http://192.168.1.100:8080这种形式除了配置域名例外还需要确认端口匹配。ATS 的例外配置一旦设置是对该域名/地址下的所有请求生效匹配判断不区分端口所以不用担心端口问题。2.3 开发环境下的本地网络请求处理如果你只是连本机起的服务比如 Mac 上跑的本地后端还涉及另一个问题本地网络权限。iOS 14 以后App 访问局域网设备会触发本地网络权限弹窗如果 Info.plist 里没有声明用途请求也会被静默拦掉表现和 ATS 拦截很像。这时候需要添加NSLocalNetworkUsageDescription到 Info.plist用于说明使用本地网络的目的。ATS 这边则建议使用NSAllowsLocalNetworking而不是全局放开keyNSAppTransportSecurity/key dict keyNSAllowsLocalNetworking/key true/ /dictNSAllowsLocalNetworking允许连接本地网络包括局域网 IP、.local域名等同时对公网请求仍保持 ATS 强制 HTTPS。这个配置在调试本地服务时比NSAllowsArbitraryLoads合理得多既解决了本地联调问题又不必对公网请求放开明文传输。重要提示如果你的调试环境只涉及局域网 IP 或 localhost优先考虑NSAllowsLocalNetworking不要图省事直接NSAllowsArbitraryLoads true。两者安全等级完全不同。3. 配置文件的写法与参数细节3.1 Info.plist 配置代码示例先看一个完整的开发调试配置包含多个维度keyNSAppTransportSecurity/key dict !-- 是否允许任意明文请求生产环境必须为 false -- keyNSAllowsArbitraryLoads/key false/ !-- 是否允许本地网络明文请求 -- keyNSAllowsLocalNetworking/key true/ !-- 是否允许加载 WebView 中的明文内容 -- keyNSAllowsArbitraryLoadsInWebContent/key false/ !-- 按域名例外 -- keyNSExceptionDomains/key dict !-- 开发环境的 HTTP 服务 -- keydev-api.example.com/key dict keyNSExceptionAllowsInsecureHTTPLoads/key true/ !-- 如果你的服务 TLS 版本较旧还需要放开下面这两项 -- keyNSExceptionMinimumTLSVersion/key stringTLSv1.0/string keyNSExceptionRequiresForwardSecrecy/key false/ /dict /dict /dict这个配置兼顾了两种场景允许本地网络明文通信同时给指定的开发域名开放了例外。公网其他域名的 HTTPS 校验不受影响。注意全局的NSAllowsArbitraryLoads仍然是false保持了安全底线。3.2 几个容易混淆的参数NSExceptionAllowsInsecureHTTPLoads针对例外域名的明文放行开关。配合前面的NSExceptionDomains使用单独放在顶层没有意义。NSExceptionRequiresForwardSecrecy是否要求前向保密。前向保密是 TLS 协议中的一种安全属性保证即使服务器私钥泄露历史通信内容也无法被解密。如果你的服务端 TLS 配置比较旧不支持前向保密就需要把这一项设为false否则请求会被 ATS 拦截。现在主流服务端都已经支持一般不需要动它。NSExceptionMinimumTLSVersion允许的最低 TLS 版本。如果服务端还在用 TLS 1.0而 ATS 要求最低 1.2就需要在这里显式声明。不过从 2022 年起苹果已经在逐步收紧 TLS 1.0/1.1 的支持生产环境还是尽快让服务端升级证书和协议更稳妥。NSAllowsArbitraryLoadsInWebContent只影响 WebView 加载的网页内容不影响原生网络请求。如果你在 App 里用 WKWebView 加载了一些 HTTP 页面可以通过它来放行网页中的明文加载而不必动原生请求的 ATS 策略。3.3 为什么同一个配置在不同版本上表现不同ATS 的行为受 iOS 版本影响很大。早期版本对NSAllowsArbitraryLoadsInWebContent和NSAllowsLocalNetworking的支持不完整某些配置在某些系统版本上会被忽略。所以调试时如果发现配置加了还是不生效先确认当前系统版本是否支持该字段。iOS 10 之前没有NSAllowsLocalNetworking那时候本地网络请求只能靠全局放行或域名例外。如果你维护的项目要兼容 iOS 9/10需要额外做版本判断。我这里建议直接翻苹果官方文档的 ATS 章节里面有一个表格列出了每个字段适用的最低系统版本对照着查比盲试快得多。4. 生产环境的正解迁移 HTTPS 才是终点4.1 为什么说临时配置不该出现在线上这个问题值得多说几句。把NSAllowsArbitraryLoads设为true带进生产环境虽然 App 功能能跑但代价是全部网络明文。在等保合规、金融审核、隐私合规这些场景下这是不可碰的红线。更重要的是苹果对 ATS 例外配置的审核越来越严格。如果你的 App 没有足够充分的理由就全局放开 ATS审核可能被驳回即使用域名例外审核人员也会要求你说明为什么这些域名不能使用 HTTPS。整体趋势是苹果在不断收紧例外范围长远看HTTP 接口只会越来越难在 iOS 上通过审核。所以生产环境的正解只有一条让所有接口都走 HTTPS。4.2 HTTPS 证书与 ATS 的配套要求上了 HTTPS 不等于万事大吉证书质量不过关ATS 照样拦截。常见的几个坑证书过期或证书链不完整系统无法完成信任链校验请求直接失败证书部署时只配了站点证书没配中间证书这种情况 PC 浏览器可能能打开但 iOS 上会因链校验失败而拒绝连接证书使用了较老的签名算法比如 SHA-1ATS 同样不认。评估一个 HTTPS 接口是否符合 ATS 要求可以在 Mac 上用命令行快速检查也可以在浏览器开发者工具里看“安全”一栏的详细信息。如果证书链是完整且受信的iOS 上通常就没有问题。4.3 老接口改造的过渡方案老项目一锅端迁移 HTTPS 不现实尤其是接口特别多、后端排期长的情况。我之前处理过一个历史项目几十个接口分布在多个域名下后端短期内没法全部切换。当时的过渡方案是前端代码封装统一的网络层把 baseURL 抽象成配置项方便切换后端优先给主域名配置 HTTPS 证书其他域名先通过域名例外临时放行上线后逐步把流量切到 HTTPS 域名每切换一个就从 Info.plist 里删掉对应的例外配置全部切完后把NSExceptionDomains清空保证 ATS 策略回到默认的安全状态。这个方案的思路是不是一次性完成迁移而是把过渡期的例外控制在一个明确、可回收的范围内并且所有例外都有对应的回收计划。每删掉一个例外配置就代表系统往完全 HTTPS 的目标推进了一步。注意域名例外配置不是上线了就忘掉的东西它应该像技术债一样被登记、追踪、定期清理。我见过不少项目的 Info.plist 里躺着一堆几年前遗留的域名例外这些就是潜在的风险点。5. 调试、排查与常见问题速查5.1 从错误日志反向定位问题ATS 拦截时的报错通常长这样NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9802) App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your apps Info.plist file.看到cleartext HTTP字样基本可以断定是 ATS 层面拦截。排查路径很直接先看请求 URL 是不是 HTTP如果是 HTTP检查NSAppTransportSecurity相关配置是否覆盖了这个域名如果配置已经覆盖但请求还是失败检查字段名大小写、层级结构如果 URL 是 HTTPS但报错带SSL或TLS关键字方向转向证书问题。这里有两个容易忽略的小点。第一NSAppTransportSecurity这个 key 的大小写不能错词典拼错不会报编译错误但系统根本没有读取到配置。第二从缓存里发出的请求不会触发 ATS 报错遇到“改完配置还是不行”的情况先清理一下 App 缓存或者卸载重装再测避免被旧缓存误导。5.2 典型问题对照表问题现象可能原因排查方向报错提示 cleartext HTTPATS 拦截 HTTP 请求按域名加例外或改用 HTTPS提示证书/SSL 错误证书链不完整或 TLS 版本过低检查证书部署与 TLS 配置局域网地址请求失败本地网络权限未开启添加NSLocalNetworkUsageDescriptionWebView 加载 HTTP 白屏NSAllowsArbitraryLoadsInWebContent未配置添加该字段并设为true改配置后仍报错plist 层级/大小写错误检查字段拼写和结构仅真机报错模拟器正常网络环境或证书信任问题确认测试机证书信任状态请求偶发失败服务端强制跳转 HTTPS 但证书不匹配检查服务端重定向规则5.3 平时容易踩的几个坑坑一域名例外配置了但用了通配符域名。之前有个同事把例外域名写成*.example.com结果实际请求的域名没有享受到例外拦截依旧。ATS 的NSExceptionDomains不支持通配符匹配必须写具体的域名。如果子域名多每个都要单独加。坑二开发环境用 IP 直连配置了但不同系统版本行为不同。iOS 对 IP 地址和.local域名的 ATS 处理有版本差异。如果你发现 IP 直连在 iOS 15 上正常在 iOS 17 上报错大概率不是因为 ATS 配置失效而是系统对本地网络的权限策略变了。处理方法是把NSAllowsLocalNetworking配上同时补充本地网络用途描述。坑三证书链不完整导致线上请求大面积失败。这个只在生产环境出现过后端把证书配好后Android 端正常iOS 端全部失败查了一圈才发现中间证书没配置。用在线检测工具查一下证书链很容易就能看到缺的环节。坑四清理缓存前反复测试。改完配置不重装 App旧请求结果被缓存或连接池复用给人“配置没生效”的错觉。我现在的习惯是改完 Info.plist 后直接卸载重装省去很多无谓排查。5.4 实用习惯与技巧给还在用 HTTP 接口调试的项目几个实操建议Debug 和 Release 用不同的 Info.plistDebug 里放宽 ATSRelease 里保持严格策略。这样开发调试方便打包上线时也不用手动删配置不容易漏。在网络层封装一个环境切换功能调试时能快速指向不同环境的 baseURL配合域名例外使用比每次改代码重新打包效率高很多。遇到 ATS 相关报错先在官方文档查字段确认系统版本要求再去动配置。网上资料更新速度跟不上系统迭代尤其新版 iOS 发布后旧教程特别容易误导人。写在最后的个人体会iOS 开发者在 HTTP 请求上栽的跟头追根究底都是一个认知问题没搞懂 ATS 是“安全策略”而不仅仅是“访问限制”。它拦你不是因为系统故意刁难而是它在替你守着数据安全的底线。我在踩过几次坑之后现在处理这类问题有个固定流程先确认是开发环境还是生产环境开发环境用NSAllowsLocalNetworking加域名例外生产环境坚决不碰全局放行每次改动配置都在注释里写清楚为什么加、什么时候可以删。这样做的好处是项目里每一个 ATS 例外都有据可查迭代几年后也不会留下说不清道不明的“历史配置”。再分享一个小技巧改配置前先看一眼控制台的完整报错不要只看第一行。ATS 的报错信息里通常带着非常明确的字段名比如 “Info.plist” 后面的 key 路径照着那个路径去改就不会出错。很多时候问题不是解决不了而是我们根本没仔细看系统给出的提示就急着去抄别人的答案了。
阅读完成 · 觉得有帮助?
咨询建站