iOS

来源:互联网 发布:天河工商局软件地址 编辑:程序博客网 时间:2024/06/05 22:30

首先说明一下,要解决的问题:DNS劫持。

对,就是要解决DNS劫持这个问题。不太懂网络的同学们可能不太懂什么是DNS,什么又是DNS劫持,这里简单介绍一下。

DNS就是域名解析系统,就是把我们平时用的网址域名(如www.baidu.com   www.sina.com.cn)解析成相对应的服务器IP,只有解析成IP之后,网络请求才能找到服务器。

DNS劫持是啥呢?更简单,就是有人把你的域名解析成了其他的IP,进而达到给你返回他想要返回的内容,比如广告、钓鱼等等.........

那么,iOS APP哪里会遇到DNS劫持的问题?当然是我们的UIWebView和WKWebView。当我们的webView加载网页时,很容易被DNS劫持,从而产生很多小广告之类的东西。为了防止这些现象,我们引入了HTTPDNS


OK!说明白了要解决的问题,那接着看HTTPDNS是怎么工作的。

其实很简单,就是阿里云自己提供了一套可靠的DNS系统,我们每次要做域名解析时,直接去阿里云上取IP,而不用通常的DNS方案,所以除了阿里云可以进行DNS劫持,其他人都没戏了(我们是相信阿里云的)。


HTTPDNS植入iOS APP 方案:

我们需要把所有的APP产生的请求(或者我们想做HTTPDNS的请求)都通过HTTPDNS来进行域名解析,通过NSURLProtocol类即可实现。

NSURLProtocol可以接管app所发出的所有请求。NSURLProtocol是抽象类,需要创建一个子类才能使用。在子类中重写必要的方法,来实现响应的接管功能:

(1)

第一步:判断哪些request需要拦截,需要返回YES;不需要返回NO
+ (BOOL)canInitWithRequest:(NSURLRequest *)request

(2)

第二步:对拦截的request进行处理,修改host,添加cookie等
+ (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request
(3)

第三步:建立NSURLSesion对象,并发起请求
- (void)startLoading
(4)

第四步:停止请求
- (void)stopLoading

(5)
第五步:NSURLSessionDelegate中,完成
请求返回数据的回吐,即将请求返回的数据回吐给client(本来请求的发起者,如UIWebView)
https证书的校验


拦截流程图:


拦截之后的处理流程图:




////  CustomURLProtocol.h//  iphone-pay////  Created by HLYUE on 2017/11/7.//  Copyright © 2017年 RHJX Inc. All rights reserved.//#import <Foundation/Foundation.h>@interface CustomURLProtocol : NSURLProtocol@end////  CustomURLProtocol.m//  iphone-pay////  Created by HLYUE on 2017/11/7.//  Copyright © 2017年 RHJX Inc. All rights reserved.//#import "CustomURLProtocol.h"#import <AlicloudHttpDNS/AlicloudHttpDNS.h>static NSString * const URLProtocolHandledKey = @"URLProtocolHandledKey";@interface CustomURLProtocol () <NSURLSessionDelegate>@property (nonatomic, strong) NSURLSessionDataTask *dnstask;@end@implementation CustomURLProtocol//判断哪些request需要拦截,需要返回YES;不需要返回NO+ (BOOL)canInitWithRequest:(NSURLRequest *)request {    //只处理http和https请求    NSString *scheme = [[request URL] scheme];    if ( ([scheme caseInsensitiveCompare:@"http"] == NSOrderedSame || [scheme caseInsensitiveCompare:@"https"] == NSOrderedSame)) {        //看看是否已经处理过了,防止无限循环        if ([NSURLProtocol propertyForKey:URLProtocolHandledKey inRequest:request]) {            return NO;        }                //post请求不拦截        if ([request.HTTPMethod isEqualToString:@"POST"]) {            return NO;        }                NSString *hostStr = [request.URL host];        HttpDnsService *httpdns = [HttpDnsService sharedInstance];        NSString *httpDnsIP = [httpdns getIpByHostAsync:hostStr];        if (httpDnsIP) {            return YES;        }    }    return NO;}//对拦截的request进行处理,修改host,添加cookie+ (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request {    NSMutableURLRequest *mutableReqeust = [request mutableCopy];    mutableReqeust = [self redirectHostInRequset:mutableReqeust];    return [mutableReqeust copy];}+(NSMutableURLRequest*)redirectHostInRequset:(NSMutableURLRequest*)request {    if ([request.URL host].length == 0) {        return request;    }        //从URL中获取域名    NSString *originUrlString = [request.URL absoluteString];    NSString *originHostString = [request.URL host];        NSRange hostRange = [originUrlString rangeOfString:originHostString];    if (hostRange.location == NSNotFound) {        return request;    }        HttpDnsService *httpdns = [HttpDnsService sharedInstance];    NSString *httpDnsIP = [httpdns getIpByHostAsync:originHostString];        if (httpDnsIP) {        //替换包头中的url开头的域名        NSString *urlString = [originUrlString stringByReplacingCharactersInRange:hostRange withString:httpDnsIP];        NSURL *url = [NSURL URLWithString:urlString];        request.URL = url;                //注意:若包头的host(key-value中的host)本身就是一个IP,则需要将这个IP替换成域名(该域名需要从referer中获取)        if ([self isValidIP:originHostString]) {            //从referer中获取域名            NSString *referStr = [request valueForHTTPHeaderField:@"Referer"];            NSArray *firstArray = [referStr componentsSeparatedByString:@"://"];            NSString *secondStr;            if (firstArray.count >= 2) {                secondStr = firstArray[1];            } else if (firstArray.count == 1) {                secondStr = firstArray[0];            }            if (secondStr) {                NSRange range = [secondStr rangeOfString:@"/"];                if (range.length > 0) {                    originHostString = [[secondStr componentsSeparatedByString:@"/"] firstObject];                    originUrlString = [originUrlString stringByReplacingOccurrencesOfString:httpDnsIP withString:originHostString];                }            }        }        //将从referer中取出的域名,放到请求包头的Host中        [request setValue:originHostString forHTTPHeaderField:@"Host"];                //设置http的header的cookie        NSArray *cookiesArray = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:[NSURL URLWithString:originUrlString]];        NSDictionary *cookieDict = [NSHTTPCookie requestHeaderFieldsWithCookies:cookiesArray];        NSString *cookie = [cookieDict objectForKey:@"Cookie"];        [request setValue:cookie forHTTPHeaderField:@"Cookie"];    }    return request;}/** *判断一个字符串是否是一个IP地址 **/+ (BOOL)isValidIP:(NSString *)ipStr {    if (nil == ipStr) {        return NO;    }        NSArray *ipArray = [ipStr componentsSeparatedByString:@"."];    if (ipArray.count == 4) {        for (NSString *ipnumberStr in ipArray) {            int ipnumber = [ipnumberStr intValue];            if (!(ipnumber>=0 && ipnumber<=255)) {                return NO;            }        }        return YES;    }    return NO;}- (void)startLoading {    NSMutableURLRequest *mutableReqeust = [[self request] mutableCopy];    //标识该request已经处理过了,防止无限循环    [NSURLProtocol setProperty:@YES forKey:URLProtocolHandledKey inRequest:mutableReqeust];        NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];    configuration.protocolClasses = @[[CustomURLProtocol class]];    NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];    self.dnstask = [session dataTaskWithRequest:mutableReqeust];    NSString *referStr = [mutableReqeust valueForHTTPHeaderField:@"Referer"];    NSLog(@"start loading httpDNS********************\nstart loading httpDNS \n *****url :%@\n *****host:%@ \n *****referer:%@\n", mutableReqeust.URL, [mutableReqeust valueForHTTPHeaderField:@"Host"], referStr);    [self.dnstask resume];}- (void)stopLoading {    [self.dnstask cancel];}#pragma mark NSURLSessionDelegate- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {    [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageAllowed];        completionHandler(NSURLSessionResponseAllow);}- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {    [[self client] URLProtocol:self didLoadData:data];}- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {    // 请求完成,成功或者失败的处理    if (!error) {        //成功        [self.client URLProtocolDidFinishLoading:self];    } else {        //失败        [self.client URLProtocol:self didFailWithError:error];    }}- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain {    /*     * 创建证书校验策略     */    NSMutableArray *policies = [NSMutableArray array];    if (domain) {        [policies addObject:(__bridge_transfer id) SecPolicyCreateSSL(true, (__bridge CFStringRef) domain)];    } else {        [policies addObject:(__bridge_transfer id) SecPolicyCreateBasicX509()];    }    /*     * 绑定校验策略到服务端的证书上     */    SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef) policies);    /*     * 评估当前serverTrust是否可信任,     * 官方建议在result = kSecTrustResultUnspecified 或 kSecTrustResultProceed     * 的情况下serverTrust可以被验证通过,https://developer.apple.com/library/ios/technotes/tn2232/_index.html     * 关于SecTrustResultType的详细信息请参考SecTrust.h     */    SecTrustResultType result;    SecTrustEvaluate(serverTrust, &result);    if (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed) {        return YES;    }    return NO;}- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * __nullable credential))completionHandler {    if (!challenge) {        return;    }    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;    NSURLCredential *credential = nil;         /*     * 获取原始域名信息。     */    NSString* host = [[self.request allHTTPHeaderFields] objectForKey:@"host"];    if (!host) {        host = self.request.URL.host;    }    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {        if ([self evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:host]) {            disposition = NSURLSessionAuthChallengeUseCredential;            credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];        } else {            disposition = NSURLSessionAuthChallengePerformDefaultHandling;        }    } else {        disposition = NSURLSessionAuthChallengePerformDefaultHandling;    }    // 对于其他的challenges直接使用默认的验证方案    completionHandler(disposition,credential);}@end
需要对request进行的处理说明:

1.将URL中的域名替换成HTTPDNS解析出来的IP

2.添加包头Host

3.添加自己想要添加的Cookie,这一步视需求而定


HTTPDNS的注册初始化方法,在官网中已经说的很明确了,不做过多解释

HTTPDNS SDK手册链接 https://help.aliyun.com/document_detail/30141.html?spm=5176.doc30113.6.577.e8LhPM

#pragma mark -- HttpDNSDegradationDelegate- (BOOL)shouldDegradeHTTPDNS:(NSString *)hostName {    //根据HTTPDNS使用说明,存在网络代理情况下需降级为Local DNS    if (self.configureProxies) {        return YES;    }    return NO;}- (void)configHTTPDNS {    self.configureProxies = [NetworkManager configureProxies];    //注册CustomURLProtocol(NSURLProtocol子类)    [NSURLProtocol registerClass:[CustomURLProtocol class]];        // 设置AccoutID,当您开通HTTPDNS服务时,您可以在控制台获取到您对应的Accout ID信息    HttpDnsService *httpdns = [[HttpDnsService alloc] initWithAccountID:156711];    httpdns.delegate = self;        //设置预解析域名列表    NSArray * hosts = [[NSArray alloc] initWithObjects: @"你的域名", nil];//这里写上你要通过HTTPDNS解析的域名    [httpdns setPreResolveHosts:hosts];        //是否允许HTTPDNS返回TTL过期的域名    [httpdns setExpiredIPEnabled:YES];        //本地日志log开关,测试环境打开    [httpdns setLogEnabled:NO];        //使用缓存机制    [httpdns setCachedIPEnabled:YES];}
configHTTPDNS方法需要在APPdelegate的 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions中调用

对于iOS HTTPDNS的 SNI场景说明:HTTPDNS的官网并没有成熟的SNI场景解决方案(针对iOS,安卓是有的)。所以我并没有支持SNI场景。也希望其他朋友们珍重。如有大能使用了,请不吝赐教,谢谢!!


原创粉丝点击