这个问题通常出现在客户端配置了 App Transport Security 限制,或者服务端 Nginx 默认丢弃带下划线的请求头,先检查这两处配置能快速定位大部分情况。
先说结论:多数情况是服务端丢弃了含下划线的 Header 或客户端 ATS 拦截了 HTTP 请求,需分别排查两端配置。
- 先确认请求头命名是否包含下划线或特殊字符
- 先处理服务端 Nginx 的 underscores_in_headers 配置
- 再验证客户端 Info.plist 的 ATS 设置及网络日志
快速处理思路
如果没有权限立刻修改代码,建议先联系服务端运维评估安全风险后调整 Nginx 配置,或在客户端 Info.plist 中放宽 ATS 限制进行测试。以下是关键配置片段:
# Nginx 配置示例
http {
underscores_in_headers on;
}
# iOS Info.plist 示例
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>注意:NSAllowsArbitraryLoads 仅用于调试,生产环境建议配置具体的 NSExceptionDomains。
为什么会这样
HTTP 协议本身对 Header 名称没有严格限制,但具体实现会有差异。iOS 系统中的 App Transport Security (ATS) 默认强制要求 HTTPS,如果 API 是 HTTP 会被拦截。另一方面,Nginx 作为广泛使用的 Web 服务器,默认配置为了安全起见会丢弃包含下划线的请求头,这是为了防止某些代理服务器混淆 Header 名称。
此外,部分网络框架在遇到 301/302 重定向时,默认不会携带 Authorization 头到新的域名,这也是导致 401 的常见原因之一。具体占比需结合实际业务日志分析,通常以下划线过滤和 ATS 拦截最为常见。
分步处理
第一步:检查客户端 Header 命名与代码设置
确认代码中设置的 Header 名称是否包含下划线(例如 X_Custom_Auth)。如果有,尝试改为中划线(X-Custom-Auth)或驼峰命名,看服务端是否能收到。以下是 iOS 原生 URLSession 设置 Header 的示例:
let url = URL(string: "https://api.example.com/login")!
var request = URLRequest(url: url)
// 建议使用中划线,避免下划线被过滤
request.setValue("Bearer token123", forHTTPHeaderField: "X-Custom-Auth")
request.httpMethod = "POST"
let task = URLSession.shared.dataTask(with: request) { data, response, error in
// 处理响应
}若使用 Alamofire 等第三方库,原理相同,需在 RequestAdapter 或默认 Header 中配置。
第二步:检查服务端 Nginx 配置
登录服务器,查看 Nginx 配置文件(通常在 /etc/nginx/nginx.conf 或 /etc/nginx/conf.d/ 下)。查找是否设置了 underscores_in_headers off; 或未设置该指令。默认为 off,需显式开启:
http {
underscores_in_headers on;
}修改后执行 nginx -t 检查语法,然后 nginx -s reload 重载配置。
第三步:检查 iOS ATS 配置
打开 Xcode 项目,检查 Info.plist 文件。如果 API 地址是 HTTP,需要配置 ATS 例外。建议不要直接开启 NSAllowsArbitraryLoads,而是针对特定域名配置:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>api.example.com</key>
<dict>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
</dict>第四步:查看服务端日志
登录服务器,使用以下命令实时查看 Nginx 访问日志,确认请求头是否到达服务端:
# 常见日志路径
sudo tail -f /var/log/nginx/access.log
# 或
sudo tail -f /usr/local/nginx/logs/access.log
# 过滤特定接口日志
sudo tail -f /var/log/nginx/access.log | grep "login"如果日志中未显示自定义 Header,说明可能在 Nginx 之前被过滤或未正确配置 log_format。
怎么验证是否生效
服务端验证
在 Nginx 日志中查看请求头。如果开启了 underscores_in_headers,带下划线的 Header 应该会出现在日志变量中(需在 log_format 中定义)。也可以使用 curl 模拟请求测试:
curl -v -H "X_Custom_Auth: token123" https://your-api.com/test观察服务端应用日志是否打印出该 Header。
客户端验证
在 Xcode 中使用 Network Logger 或第三方抓包工具(如 Charles,需配置证书)查看实际发出的请求。确认 Header 是否已附加,以及是否被系统移除。如果 ATS 配置生效,HTTP 请求应能正常发出而不被系统拦截。
常见坑
1. 重定向丢 Header:如果 API 返回 301/302 跳转到另一个域名,NSURLSession 默认不会携带 Authorization 头。需要在代理 delegate 中手动处理或确保 API 不跨域重定向。
2. Header 大小写:HTTP 标准不区分大小写,但部分服务端代码区分。建议统一使用标准写法,如 Authorization。
3. ATS 临时例外失效:iOS 10 以后,NSTemporaryExceptionAllowsInsecureHTTPLoads 有时在真机上表现与模拟器不一致,发布前务必在真机测试。
4. WAF 拦截:如果服务端前有 Cloudflare 或其他 WAF,它们也可能过滤特定 Header,需检查 WAF 日志。
安全风险与替代方案
1. Nginx 下划线配置风险
开启 underscores_in_headers on 可能在多层代理架构中引发 HTTP 请求走私(Request Smuggling)风险,因为某些代理服务器会将下划线转换为中划线,导致后端解析不一致。
替代方案:优先修改客户端 Header 命名,使用中划线(-)代替下划线(_),这是最安全且兼容性最好的做法。
2. ATS 宽松配置风险
NSAllowsArbitraryLoads 若未严格限制域名会降低 App 传输安全性,可能导致 App Store 审核被拒或用户数据泄露。
替代方案:生产环境务必使用 NSExceptionDomains 指定具体域名,并确保服务端支持 HTTPS。
参考来源
- Apple Developer Documentation, Information Property List: NSAppTransportSecurity, URL: https://developer.apple.com/documentation/bundleresources/information_property_list/nsapptransportsecurity
- Nginx Official Documentation, ngx_http_core_module: underscores_in_headers, URL: https://nginx.org/en/docs/http/ngx_http_core_module.html#underscores_in_headers