AFNetworking 原作者都无法解决的问题: 如何使用ip直接访问https网站?

背景

最近App似乎有报异常是DNS无法解析,尝试解决此问题.搜集到的资料很少,甚至连AFN原作者都判定这可能是一个无解的问题,参见: https://github.com/AFNetworking/AFNetworking/issues/2954,不过最终还是靠着stackoverflow上的一丁点提示,顺利找到并汇集成了一个可用的解决方案.大喜,与君共享!

问题描述

通过IP直接访问网站,可以解决DNS劫持问题.DNS劫持,可以通过修改电脑的host文件模拟.如果是HTTP请求,使用ip地址直接访问接口,配合header中Host字段带上原来的域名信息即可;如果是 https请求,会很麻烦,需要 Overriding TLS Chain Validation Correctly;curl 中有一个 -resolve 方法可以实现使用指定ip访问https网站,iOS中集成curl库应该也可以,不过改动太大,未验证;对于服务器IP经常变的情况,可能需要使用httpDNS服务,参见:https://www.dnspod.cn/httpdns.

解决方案讨论

1. 最直接的方式是允许无效的SSL证书,生产环境不建议使用;

2.一个需要部分重写AFN源码的方法.

  • 在Info.plist中添加NSAppTransportSecurity类型Dictionary,在NSAppTransportSecurity下添加NSAllowsArbitraryLoads类型Boolean,值设为YES.这些本来是用来解决iOS9下,允许HTTP请求访问网络的,当然作用不止这些.具体原因感兴趣的自行google.
  • 给 AFURLConnectionOperation 类添加新属性:
/** 可信任的域名,用于支持通过ip访问此域名下的https链接.
 Trusted domain, this domain for support via IP access HTTPS links.
 */
@property(nonatomic, strong) NSMutableArray * trustHostnames;
  • 给 AFURLConnectionOperation 实现的代理方法: - (void)connection:(NSURLConnection )connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge )challenge 添加添加可信任的域名的相关逻辑代码:
- (void)connection:(NSURLConnection *)connection
willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
    if (self.authenticationChallenge) {
        self.authenticationChallenge(connection, challenge);
        return;
    }
    
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
        SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
        
        /* 添加可信任的域名,以支持:直接使用ip访问特定https服务器.
         Add trusted domain name to support: direct use of IP access specific HTTPS server.*/
        for (NSString * trustHostname  in [self trustHostnames]) {
            serverTrust = AFChangeHostForTrust(serverTrust, trustHostname);
        }

    ....
  • 参考Apple官方文档,实现自定义的添加可信域名的函数: AFChangeHostForTrust
static inline SecTrustRef AFChangeHostForTrust(SecTrustRef trust, NSString * trustHostname)
{
    if ( ! trustHostname || [trustHostname isEqualToString:@""]) {
        return trust;
    }
    
    CFMutableArrayRef newTrustPolicies = CFArrayCreateMutable(
                                                              kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
    
    SecPolicyRef sslPolicy = SecPolicyCreateSSL(true, (CFStringRef)trustHostname);
    
    CFArrayAppendValue(newTrustPolicies, sslPolicy);
    
    
#ifdef MAC_BACKWARDS_COMPATIBILITY
    /* This technique works in OS X (v10.5 and later) */
    
    SecTrustSetPolicies(trust, newTrustPolicies);
    CFRelease(oldTrustPolicies);
    
    return trust;
#else
    /* This technique works in iOS 2 and later, or
     OS X v10.7 and later */
    
    CFMutableArrayRef certificates = CFArrayCreateMutable(
                                                          kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
    
    /* Copy the certificates from the original trust object */
    CFIndex count = SecTrustGetCertificateCount(trust);
    CFIndex i=0;
    for (i = 0; i < count; i++) {
        SecCertificateRef item = SecTrustGetCertificateAtIndex(trust, i);
        CFArrayAppendValue(certificates, item);
    }
    
    /* Create a new trust object */
    SecTrustRef newtrust = NULL;
    if (SecTrustCreateWithCertificates(certificates, newTrustPolicies, &newtrust) != errSecSuccess) {
        /* Probably a good spot to log something. */
        
        return NULL;
    }
    
    return newtrust;
#endif
}
  • 使用AOP方法,重写 AFURLConnectionOperation 的trustHostnames属性:
    /* 使用AOP方式,指定可信任的域名, 以支持:直接使用ip访问特定https服务器.*/
    [AFURLConnectionOperation aspect_hookSelector:@selector(trustHostnames) withOptions:AspectPositionInstead usingBlock: ^(id<AspectInfo> info){
        __autoreleasing NSArray * trustHostnames = @[@"www.example.com"];
        
        NSInvocation *invocation = info.originalInvocation;
        [invocation setReturnValue:&trustHostnames];
    }error:NULL];

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏王小雷

超详细讲解Sqoop2应用与实践

摘要:超详细讲解Sqoop2应用与实践,从hdfs上的数据导入到postgreSQL中,再从postgreSQL数据库导入到hdfs上。详细讲解创建link和创...

36210
来自专栏bboysoul

如何在metasploit中使用shodan

首先打开metasploit sudo service postgresql start msfconsole

903
来自专栏计算机视觉

Premature end of JPEG file(不完整的图像)

错误代码 “[[Node: DecodeJpeg = DecodeJpegacceptable_fraction=1, channels=0, dct_met...

872
来自专栏GopherCoder

『No18: Go 实现世界杯后台管理系统』

趁着周末更新一期,上一期讲到 如何快速熟悉一个项目, 文章的最后讲到,最好的方法是借用相同的技术栈重新实现一个项目。

1051
来自专栏xx_Cc的学习总结专栏

iOS-网络编程(三)AFNetworking使用

2647
来自专栏GuZhenYin

用SignalR 2.0开发客服系统[系列5:使用SignalR的中文简体语言包和其他技术点]

前言 交流群:195866844 目录: 用SignalR 2.0开发客服系统[系列1:实现群发通讯] 用SignalR 2.0开发客服系统[系列2:实现聊天室...

2129
来自专栏Golang语言社区

转--在学Go语言

开始学点儿Go语言,这语言据说在国内比在国外火,社区上褒贬不一,不过“小马过河”嘛,总要先自己试试再来下结论。 环境准备: 1.在Golang中国下载安装go语...

3457
来自专栏Android开发指南

13.按比例显示图片、自定义属性、测量

35210
来自专栏小蠢驴iOS专题

手把手教你使用Bugly收集线上崩溃信息

3823
来自专栏晓晨的专栏

IdentityServer(11)- 使用Hybrid Flow并添加API访问控制

754

扫码关注云+社区