iOS App 请求 API 鉴权头 Header 被过滤导致 401 怎么配置?

文章导读
这个问题通常出现在客户端配置了 App Transport Security 限制,或者服务端 Nginx 默认丢弃带下划线的请求头,先检查这两处配置能快速定位大部分情况。
📋 目录
  1. A 快速处理思路
  2. B 为什么会这样
  3. C 分步处理
  4. D 怎么验证是否生效
  5. E 常见坑
  6. F 安全风险与替代方案
  7. G 参考来源
A A

这个问题通常出现在客户端配置了 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。

iOS App 请求 API 鉴权头 Header 被过滤导致 401 怎么配置?

怎么验证是否生效

服务端验证

在 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