iOS开发中ATS配置详解:解决HTTP请求失效与安全实践

1. 项目概述:为什么iOS开发中HTTP请求会“失灵”?

如果你刚开始接触iOS开发,或者刚从其他平台转过来,可能会被一个看似简单的问题卡住:为什么我写的网络请求,在模拟器里跑得好好的,一到真机上就“哑火”了,或者直接报错?控制台里可能还会出现类似“App Transport Security policy requires the use of a secure connection”这样的警告。这背后,就是苹果从iOS 9开始引入并逐步强化的App Transport Security机制,也就是我们常说的ATS。

简单来说,ATS是苹果为了提升用户数据安全而设立的一道“安检门”。它默认要求所有从App发起的网络通信都必须使用安全的HTTPS协议,并且要满足一系列严格的安全标准,比如使用TLS 1.2及以上版本的加密套件、证书必须由受信任的证书颁发机构签发等。而传统的、不加密的HTTP协议,在ATS的默认规则下,是被直接“拒之门外”的。这也就是你项目标题里提到的“不能使用HTTP请求”问题的根源。

但现实开发中,我们总会遇到一些“特殊情况”。比如,你正在对接一个还在开发中的后端API,它暂时只提供了HTTP接口;或者你需要连接到一个本地测试服务器(http://localhosthttp://192.168.x.x);甚至是一些老旧的内网服务,根本没有升级HTTPS的计划。在这些场景下,我们不可能因为ATS的规则就停止开发。因此,学会如何安全、合规地“绕过”ATS的默认限制,是每一位iOS开发者必须掌握的技能。这绝不是教你“开后门”,而是在理解安全规则的基础上,进行必要的、可控的配置调整,以满足开发和测试阶段的合理需求。

2. 核心原理与配置方案深度解析

要解决问题,首先要理解规则。ATS的配置核心在于你项目中的Info.plist文件。这个文件是iOS应用的“身份证”和“配置清单”,ATS的相关设置就通过向其中添加特定的键值对来实现。

2.1 ATS配置的三种核心思路

面对HTTP需求,我们通常有三种配置思路,从“一刀切”到“精细控制”,安全性和灵活性各不相同。

2.1.1 全局放行:NSAllowsArbitraryLoads

这是最“简单粗暴”也最不推荐在生产环境中使用的方法。通过在Info.plist中添加NSAllowsArbitraryLoads并将其设置为YES,你相当于告诉系统:“我这个App里的所有网络请求,不管是HTTP还是HTTPS,都别管了,全部放行。”

<key>NSAppTransportSecurity</key> <dict> <key>NSAllowsArbitraryLoads</key> <true/> </dict>

为什么强烈不推荐?这完全违背了ATS设立的初衷,将用户数据暴露在风险之中。苹果的App Store审核指南明确不鼓励这种做法,如果你的App没有足够充分的理由(例如一个网络浏览器App),提交审核时很可能会被拒绝。它只应在内部开发、测试或原型验证阶段临时使用。

2.1.2 域名例外:NSExceptionDomains

这是最推荐、最符合安全最佳实践的方式。它的思路是:默认遵守ATS的严格规则(即全局禁用HTTP),但为某些特定的、你明确知晓的域名“开绿灯”。

<key>NSAppTransportSecurity</key> <dict> <key>NSExceptionDomains</key> <dict> <key>your-insecure-server.com</key> <dict> <!-- 允许该域名使用HTTP --> <key>NSExceptionAllowsInsecureHTTPLoads</key> <true/> <!-- 可选:是否包含子域名,如api.your-insecure-server.com --> <key>NSIncludesSubdomains</key> <true/> </dict> </dict> </dict>

这种方式实现了精准控制。只有你列出的your-insecure-server.com及其子域名可以使用HTTP,其他任何未声明的域名依然必须使用HTTPS。这既满足了连接特定HTTP服务的需求,又最大程度地保障了App整体的网络安全。

2.1.3 混合配置:全局禁用ATS但启用例外

这是一种折中方案,先通过NSAllowsArbitraryLoads全局放行,再通过NSExceptionDomains对某些重要的、已升级HTTPS的域名重新启用ATS安全要求。

<key>NSAppTransportSecurity</key> <dict> <key>NSAllowsArbitraryLoads</key> <true/> <key>NSExceptionDomains</key> <dict> <key>your-secure-api.com</key> <dict> <!-- 对该域名重新启用ATS要求 --> <key>NSExceptionRequiresForwardSecrecy</key> <true/> <key>NSExceptionMinimumTLSVersion</key> <string>TLSv1.2</string> </dict> </dict> </dict>

这种配置适用于一个App需要访问大量不可控的HTTP资源(如一个内置的Web浏览器组件),但同时又要确保与自家核心API(your-secure-api.com)的通信是绝对安全的场景。它比纯全局放行更安全,但比纯域名例外更宽松。

2.2 配置实操与Xcode中的可视化编辑

知道了键值对,我们如何在项目中添加呢?有两种主流方式。

2.2.1 直接编辑Info.plist源码

在Xcode的项目导航器中找到Info.plist文件,右键选择Open As -> Source Code,就可以看到其XML格式的源码。找到<dict>标签对,将上述配置代码插入到最后一个</dict>结束标签之前即可。这种方式直接、准确,适合熟悉配置的开发者。

2.2.2 使用Xcode的属性列表编辑器

对于新手,更推荐使用可视化编辑器。在Xcode中右键点击Info.plist,选择Open As -> Property List。然后:

  1. 鼠标悬停在任意一个Key上,点击右侧出现的+按钮。
  2. 在新增的Key字段中,手动输入App Transport Security Settings。Xcode通常会自动补全,并将其内部Key识别为NSAppTransportSecurity
  3. 点击这个Key右侧的Type列,将其从String改为Dictionary
  4. 点击App Transport Security Settings这一行左侧的展开三角箭头,再次点击其右侧的+按钮。
  5. 此时,你可以从下拉菜单中选择或手动输入子Key,例如Allow Arbitrary Loads(对应NSAllowsArbitraryLoads),并将其Value设置为YES
  6. 如果需要添加域名例外,则添加一个Key为Exception Domains(对应NSExceptionDomains),类型为Dictionary的项,然后在其下继续添加子字典。

注意:Xcode的可视化编辑器有时会“隐藏”真正的Key名,显示为更易读的“描述”。当你用源码方式打开时,看到的才是标准的Key(如NSAllowsArbitraryLoads)。两种方式效果完全等价,选择你习惯的即可。

3. 实战场景与进阶处理方案

掌握了基础配置,我们来看看几个更具体的实战场景,这些往往是新手容易踩坑的地方。

3.1 连接本地开发服务器与局域网IP

开发中最常见的需求就是连接本机的http://localhost:8080或同局域网的http://192.168.1.100:3000。由于这些地址没有域名,无法使用NSExceptionDomains来配置。此时,NSAllowsArbitraryLoads是唯一的选择。

但这里有一个大坑:从iOS 10开始,苹果加强了对本地网络和任意负载的限制。即使你在Info.plist中设置了NSAllowsArbitraryLoadsYES,对于localhost127.0.0.1,ATS默认仍然是放行的。然而,对于像192.168.x.x这样的局域网IP地址,仅仅设置NSAllowsArbitraryLoads可能依然不够

解决方案:你需要为具体的IP地址也配置例外域。是的,IP地址也可以作为“域名”添加到NSExceptionDomains中。

<key>NSAppTransportSecurity</key> <dict> <key>NSExceptionDomains</key> <dict> <key>192.168.1.100</key> <dict> <key>NSExceptionAllowsInsecureHTTPLoads</key> <true/> <!-- 特别注意:对于IP地址,通常必须设置下面这个为NO --> <key>NSIncludesSubdomains</key> <false/> <!-- 可选:如果服务器使用自签名证书,还需添加此条 --> <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key> <true/> </dict> </dict> </dict>

实操心得:在iOS真机调试连接局域网服务器时,如果遇到请求失败,首先检查手机和电脑是否在同一个Wi-Fi网络,然后优先尝试为服务器IP地址配置NSExceptionDomains,这比单纯开启全局任意加载更可靠、更安全。

3.2 处理HTTPS证书问题(自签名、过期、域名不匹配)

有时候,问题不是HTTP,而是HTTPS不符合ATS的要求。例如,开发测试环境使用了自签名证书,或者证书过期,或者证书绑定的域名与实际访问的域名不匹配。ATS会拒绝这样的连接。

对于自签名证书,除了像上面一样在NSExceptionDomains中配置NSExceptionAllowsInsecureHTTPLoads,更常见的做法是在代码层面进行“证书锁定”或“信任覆盖”。这通常通过实现URLSession的代理方法urlSession(_:didReceive:completionHandler:)来完成。

import Foundation class UnsafeNetworkManager: NSObject, URLSessionDelegate { func allowSelfSignedCertificates() { let configuration = URLSessionConfiguration.default let session = URLSession(configuration: configuration, delegate: self, delegateQueue: nil) // ... 使用这个session发起请求 } // URLSessionDelegate 方法 func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust { // 强制信任该服务器的证书(仅限调试!) if let serverTrust = challenge.protectionSpace.serverTrust { let credential = URLCredential(trust: serverTrust) completionHandler(.useCredential, credential) return } } completionHandler(.performDefaultHandling, nil) } }

警告:上述代码会接受任何服务器的证书,包括无效或恶意的证书,这极度危险,只能用于测试环境。在生产环境中,你应该使用“证书锁定”,即预先将合法的服务器证书或公钥哈希值内置在App中,在代理方法里进行比对,只信任完全匹配的证书。

3.3 针对特定HTTP请求的临时解决方案

如果你不想修改全局的ATS配置,或者只是临时需要发起一个HTTP请求,有没有更轻量级的方法?对于使用URLSession发起的请求,可以通过自定义URLSessionConfiguration来实现。

import Foundation func makeInsecureRequest() { // 1. 创建一个自定义的配置 let config = URLSessionConfiguration.default // 2. 设置其连接属性字典,允许任意负载(仅对该Session生效) config.connectionProxyDictionary = [:] // 注意:在iOS 15+,直接设置connectionProxyDictionary可能不够。 // 更可靠的方法是创建一个继承自URLProtocol的自定义协议,但这比较复杂。 // 更实用的临时方案:直接使用配置了例外域的URLSession,或者使用全局ATS配置。 // 对于单次请求,修改ATS配置并重启App是更常见的做法。 }

实际上,通过代码临时完全绕过ATS是非常困难且不推荐的。苹果设计ATS的意图就是在应用层面强制安全规范。因此,对于需要HTTP访问的场景,老老实实配置Info.plist是正道。

4. 网络调试技巧与常见问题排查实录

配置好了,但请求还是失败了?别急,网络问题从来都不是一次配置就能100%解决的。下面分享一些我在实际开发中积累的调试技巧和常见问题排查清单。

4.1 系统级网络日志捕获

当你的请求失败,而Xcode控制台只给出一个模糊的错误时,可以启用iOS系统的详细网络日志。

  1. 在Mac上打开“控制台”应用。
  2. 将你的iOS设备通过USB连接到Mac。
  3. 在控制台左侧设备列表中选择你的iPhone/iPad。
  4. 在右上角的搜索栏中输入nsurlsessionCFNetwork
  5. 在设备上运行你的App并触发网络请求。此时控制台会滚动输出极其详细的网络连接日志,包括DNS解析、TCP握手、TLS协商、HTTP报文等全过程。这对于诊断复杂的证书问题、重定向问题或协议错误非常有帮助。

4.2 使用网络调试代理工具

CharlesProxyman这样的抓包工具是iOS开发者的神器。它们不仅可以截获和查看所有HTTP/HTTPS请求与响应的原始数据,还能模拟慢速网络、断点修改请求/响应、映射本地文件等。

  • 配置步骤:在电脑上启动代理工具,获取其代理地址(如192.168.1.2:8888)。在iOS设备的Wi-Fi设置中,为该网络配置HTTP代理,填入电脑的IP和端口。然后在设备上安装并信任代理工具提供的根证书(用于解密HTTPS流量)。
  • 调试价值:你可以清晰地看到请求是否真的发出去了,发出的地址和头部是否正确,服务器返回了什么状态码和正文。很多时候,问题不是出在ATS,而是出在请求参数错误、服务器返回了404或500等。

4.3 常见问题速查与解决方案

我整理了一个表格,将常见错误现象、可能原因和解决方案对应起来,你可以像查字典一样快速定位问题。

错误现象/控制台日志可能原因分析解决方案与排查步骤
App Transport Security policy requires the use of a secure connection1. 尝试访问HTTP地址,但未配置ATS例外。
2. 尝试访问的HTTPS地址不符合ATS安全要求(如TLS版本低)。
1. 检查请求URL是否为http://开头。若是,按本文第2节配置Info.plist
2. 使用在线工具(如SSL Labs)检查服务器TLS配置,或检查是否为自签名证书。
CFNetwork SSLHandshake failed (-9806)/TLS handshake failedHTTPS握手失败。通常是证书问题:自签名、过期、域名不匹配、根证书不受信任。1. 开发环境:可为该域名配置NSExceptionAllowsInsecureHTTPLoads,或实现URLSessionDelegate临时信任(见3.2节)。
2. 生产环境:联系服务器管理员修复证书。
The resource could not be loaded because the App Transport Security policy requires the use of a secure connection通常是WebView加载HTTP内容时触发的ATS拦截。对于WKWebView,除了配置Info.plist,还可以在初始化时通过WKWebViewConfiguration设置:
if #available(iOS 10.0, *) { config.allowsInlineMediaPlayback = true }
但主要依赖ATS配置。
请求在模拟器成功,在真机失败模拟器的ATS策略有时比真机更宽松。真机环境才是最终标准。永远以真机测试为准。检查Info.plist配置是否正确,并确保为真机做了签名和配置。
配置了NSAllowsArbitraryLoads仍无法访问局域网IPiOS 10+ 对任意加载的限制加强,对纯IP地址的支持有额外要求。尝试将IP地址(如192.168.1.100)作为例外域名添加到NSExceptionDomains中,并设置NSIncludesSubdomainsfalse
错误码-1003/-1004找不到主机(-1003)或无法连接服务器(-1004)。这是网络层错误,在ATS之前。1. 检查URL拼写是否正确。
2. 检查设备网络是否通畅。
3. 检查服务器是否正在运行,端口是否开放。
4. 检查是否有防火墙或代理阻止了连接。
错误码-1012操作无法完成。(SSL错误)同SSL握手失败,重点检查证书问题。
使用第三方库(Alamofire等)报错底层依然是URLSession,ATS规则同样适用。排查思路同上。第三方库的错误信息可能被封装,需要查看其底层返回的原始Error对象。

4.4 配置生效与清理

  1. 生效时机:修改Info.plist后,需要重新编译并运行你的App,配置才会生效。仅仅在Xcode里修改文件是不够的。
  2. 清理缓存:有时Xcode会有缓存,导致配置修改看似没生效。可以尝试Product -> Clean Build Folder,然后删除App,重新安装运行。
  3. 多Target配置:如果你的项目有多个Target(比如主App、Today Extension、Watch App等),每个Target都有自己的Info.plist。你需要为每个需要网络访问的Target单独配置ATS。

5. 生产环境安全考量与最佳实践

在开发测试阶段,我们可以相对宽松地配置ATS。但一旦App要发布到App Store,就必须将安全提到最高优先级。

5.1 审核指南与正当理由

苹果的App Store审核指南明确提及了ATS。如果你设置了NSAllowsArbitraryLoadsYES,必须有充分的理由,否则审核会被拒绝。可被接受的正当理由包括:

  • App是一个Web浏览器。
  • App是一个网络诊断工具。
  • App需要连接用户自定义的或企业内部的服务器,而这些服务器不在开发者的控制范围内(例如,企业级MDM管理应用)。
  • App使用AVFoundation框架播放流媒体,但流媒体源是HTTP的(这种情况也有更具体的例外配置键NSAllowsArbitraryLoadsInMedia)。

对于绝大多数普通App,最安全、最易通过审核的做法是:仅使用NSExceptionDomains为少数必须的、明确的HTTP域名配置例外,并尽可能推动这些服务升级到HTTPS。

5.2 分环境配置策略

一个优秀的实践是,为开发(Development)、测试(Staging/Testing)和生产(Production)环境使用不同的ATS配置。

  • 开发/测试环境:可以配置较宽松的策略,允许连接本地HTTP服务器和测试环境的HTTP API。
  • 生产环境:使用最严格的策略,只允许HTTPS连接,或者仅为极少数无法升级的遗留服务配置HTTP例外。

如何实现?你可以创建多个Info.plist文件(如Info-Dev.plist,Info-Prod.plist),或者使用单个Info.plist但通过User-Defined Build SettingsPreprocessor Macros来根据不同的编译配置(Scheme)动态设置不同的值。更常见的做法是使用.xcconfig配置文件来管理不同环境的变量,但这需要一定的项目配置经验。

5.3 推动后端服务升级HTTPS

作为客户端开发者,我们不能只停留在“如何绕过限制”上。从长远和根本来看,推动所有服务端API升级到HTTPS才是最终的解决方案。如今,获取一个免费的、受信任的SSL证书(例如来自Let‘s Encrypt)已经非常容易。向你的后端团队说明:

  1. 安全性:HTTPS加密传输,防止数据在途中被窃听或篡改。
  2. 合规性:满足苹果ATS要求,避免未来因政策收紧导致App无法上架或运行。
  3. 用户体验:现代浏览器对HTTP网站会有“不安全”警告,影响品牌形象。iOS/macOS系统级特性如“通用链接”也要求HTTPS。
  4. 技术趋势:HTTP/2、QUIC等新一代协议都基于HTTPS,升级能获得更好的性能。

解决iOS开发中的HTTP请求问题,是一个从“知其然”(如何配置)到“知其所以然”(理解ATS安全理念)的过程。它不仅仅是添加几行配置代码,更涉及到开发流程、环境管理和安全意识。从我个人的经验来看,初期遇到这个问题时可能会觉得苹果“多事”,但当你理解了其背后保护用户数据的良苦用心,并学会如何优雅地处理例外情况时,你会发现自己对iOS网络层的理解更深了一个层次。记住,配置只是手段,构建安全可靠的App才是最终目的。在下次遇到网络请求失败时,不妨先拿出抓包工具,看看数据到底卡在了哪一步,再结合本文的思路去排查,你解决问题的效率会大大提高。