URLSession.didReceiveChallenge:如何忽略服务端认证中特定证书校验
macOS下NSURLSession处理IP地址HTTPS请求的TLS证书校验问题
问题描述
我在macOS上用原生NSURLSession发送HTTPS请求,已经实现了didReceiveChallenge回调处理服务端TLS认证。现在有个特殊场景:请求URL用的是IP地址而非域名,想移除URL与服务器证书通用名匹配的默认校验,但保留其他所有校验规则。同时想确认我当前代码是否正确,另外也想问能不能只针对特定连接取消这个校验?
更新:感谢Larme的评论,我调整了逻辑,把主机名检查绑定到特定域名而非IP,代码如下:
- (void)URLSession:(NSURLSession*)session didReceiveChallenge:(NSURLAuthenticationChallenge*)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential* credential))completionHandler { if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) { BOOL allow = true; if (URL_IS_IP) { SecTrustRef trust; trust = challenge.protectionSpace.serverTrust; OSStatus err; SecPolicyRef policy; policy = SecPolicyCreateSSL(true, CFSTR("matchinUrl.com")); err = SecTrustSetPolicies(trust, policy); if (err == errSecSuccess) { allow = evaluateTrust(trust); } CFRelease(policy); } if (allow) { completionHandler( NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:trust]); } else { completionHandler( NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil); } } else { completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); } }
代码正确性分析
- 核心逻辑合理:当请求URL是IP时,通过
SecPolicyCreateSSL指定匹配域名matchinUrl.com,替换默认的IP校验策略,这样会用指定域名校验证书的SAN/CN,而非原IP,符合你“跳过IP与证书通用名匹配,保留其他校验”的需求(证书链、有效期等校验由evaluateTrust处理)。 - 待修正问题:
trust变量仅在URL_IS_IP分支内赋值,若分支不触发,后续使用[NSURLCredential credentialForTrust:trust]会出现野指针错误,需把trust的赋值移到分支外。URL_IS_IP的判断逻辑要确保准确,避免误触发特殊校验。evaluateTrust需完整实现证书校验逻辑(比如证书链有效性、有效期、吊销状态等),不能做简单判断。
修正后的代码
- (void)URLSession:(NSURLSession*)session didReceiveChallenge:(NSURLAuthenticationChallenge*)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential* credential))completionHandler { if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) { BOOL allow = true; SecTrustRef trust = challenge.protectionSpace.serverTrust; if (URL_IS_IP) { OSStatus err; SecPolicyRef policy = SecPolicyCreateSSL(true, CFSTR("matchinUrl.com")); err = SecTrustSetPolicies(trust, policy); if (err == errSecSuccess) { allow = evaluateTrust(trust); } else { allow = false; // 策略设置失败时拒绝认证 } CFRelease(policy); } else { // 非IP请求用系统默认校验逻辑 SecTrustResultType result; OSStatus err = SecTrustEvaluate(trust, &result); allow = (err == errSecSuccess && (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed)); } if (allow) { completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:trust]); } else { completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil); } } else { completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); } }
针对特定连接取消校验的实现
可以通过两种方式实现仅针对特定连接跳过IP-证书通用名匹配:
- 方案1:给任务打标记:创建
NSURLSessionDataTask时,用task.taskDescription或自定义关联对象给需要特殊处理的任务打标记,在didReceiveChallenge中通过challenge.task判断标记,再触发特殊校验。// 创建任务时添加标记 NSURLSessionDataTask *task = [session dataTaskWithRequest:request]; task.taskDescription = @"special_ip_cert_check"; [task resume]; // 在回调中判断 if ([challenge.task.taskDescription isEqualToString:@"special_ip_cert_check"]) { // 执行自定义校验逻辑 } - 方案2:IP列表过滤:提前维护需要特殊处理的IP列表,在回调中判断
challenge.protectionSpace.host是否在列表内,仅对列表内IP应用自定义策略。
内容的提问来源于stack exchange,提问作者Zohar81
相关产品推荐
相关产品推荐

