You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

启用客户端证书认证后,NSUrlSession的NSURLAuthenticationMethodServerTrust验证失败

兄弟,我之前踩过一模一样的坑!你这问题本质是只处理了服务器信任(ServerTrust)的挑战,没接住客户端证书的认证请求,导致TLS握手流程到一半就中断报错了。当IIS开启客户端证书要求后,NSURLSession的认证流程是分两步的:先验证服务器的自签名证书,再向客户端索要证书,你只处理了第一步,自然会失败。给你一步步捋清楚怎么解决:

1. 先确保客户端证书加载正确

IIS对客户端证书的格式要求是带私钥的PFX文件,你得先把证书正确加载成SecIdentityRef——这是后续生成客户端凭证的核心。给你一段靠谱的加载代码:

// 从Bundle中加载PFX证书文件
NSString *certPath = [[NSBundle mainBundle] pathForResource:@"你的证书文件名" ofType:@"pfx"];
NSData *certData = [NSData dataWithContentsOfFile:certPath];
if (!certData) {
    NSLog(@"客户端证书文件加载失败!");
    return;
}

CFDataRef certDataRef = (__bridge CFDataRef)certData;
CFStringRef certPassword = CFSTR("你的证书密码"); // 注意密码要和PFX导出时一致

// 配置导入选项
const void *keys[] = {kSecImportExportPassphrase};
const void *values[] = {certPassword};
CFDictionaryRef importOptions = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);

CFArrayRef importItems = NULL;
OSStatus importStatus = SecPKCS12Import(certDataRef, importOptions, &importItems);
if (importStatus == errSecSuccess && CFArrayGetCount(importItems) > 0) {
    CFDictionaryRef identityDict = CFArrayGetValueAtIndex(importItems, 0);
    // 提取带私钥的身份对象,保存下来供后续使用
    self.clientIdentity = (SecIdentityRef)CFDictionaryGetValue(identityDict, kSecImportItemIdentity);
    CFRetain(self.clientIdentity); // 记得要retain,避免被提前释放
} else {
    NSLog(@"PFX证书解析失败,错误码:%d", (int)importStatus);
}

// 释放资源
if (importItems) CFRelease(importItems);
CFRelease(importOptions);

这里要注意:PFX文件必须包含私钥,密码要和导出时完全一致,不然会解析失败。

2. 完整处理NSURLSession的认证挑战

你之前只处理了NSURLAuthenticationMethodServerTrust,现在必须加上NSURLAuthenticationMethodClientCertificate的处理逻辑,完整的挑战处理代码如下:

- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler {
    NSString *authMethod = challenge.protectionSpace.authenticationMethod;
    
    // 处理服务器自签名证书信任
    if ([authMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
        SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
        // 如果你想做更严格的验证(比如对比证书指纹),可以在这里添加自定义逻辑
        NSURLCredential *serverCredential = [NSURLCredential credentialForTrust:serverTrust];
        completionHandler(NSURLSessionAuthChallengeUseCredential, serverCredential);
    }
    // 处理客户端证书认证请求
    else if ([authMethod isEqualToString:NSURLAuthenticationMethodClientCertificate]) {
        if (self.clientIdentity) {
            // 生成客户端凭证,certificates传nil即可(系统会自动从identity中提取证书链)
            NSURLCredential *clientCredential = [NSURLCredential credentialWithIdentity:self.clientIdentity certificates:nil persistence:NSURLCredentialPersistenceNone];
            completionHandler(NSURLSessionAuthChallengeUseCredential, clientCredential);
        } else {
            // 没有可用的客户端证书,取消请求
            NSLog(@"无可用客户端证书,取消认证");
            completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
        }
    }
    // 其他未知认证类型,交给系统默认处理
    else {
        completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
    }
}

关键是:处理完ServerTrust之后,系统会立刻发起ClientCertificate的挑战,你必须在同一个代理方法里处理这个请求,不然服务器会因为没收到客户端证书而返回403或TLS握手错误。

3. 检查IIS的配置细节

别光改代码,服务器端的配置也容易踩坑:

  • 确保IIS站点的SSL设置里,「客户端证书」选项设置为「要求」或「接受」(如果是测试可以先设为「接受」,避免强制要求导致握手失败);
  • 自签名场景下,要把服务器的根证书导入到iOS设备的信任列表里(或者在App的ServerTrust处理逻辑里跳过系统验证,只做自定义指纹校验);
  • 检查应用程序池的身份是否有访问服务器证书存储的权限,不然IIS可能无法验证客户端证书的有效性。

4. 排查错误的小技巧

如果还是报错,建议:

  • 看iOS控制台的日志,里面会有具体的错误信息(比如NSURLErrorDomain Code=-1206就是证书相关的错误);
  • 用Charles抓包,看TLS握手阶段的交互,确认是哪一步出了问题(比如服务器是否发送了「Certificate Request」消息)。

按照这几步来,应该就能解决你的问题了!

内容的提问来源于stack exchange,提问作者Tobe

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 07:26:47