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场景。也希望其他朋友们珍重。如有大能使用了,请不吝赐教,谢谢!!
- iOS
- iOS
- IOS
- iOS
- iOS
- IOS
- ios
- iOS
- iOS
- IOS
- iOS
- ios
- ios ~~~~~
- ios
- IOS
- IOS
- IOS
- ios
- Eclipse中的快捷键快速生成常用代码(例如无参、带参构造,set、get方法),以及Java中重要的内存分析(栈、堆、方法区、常量池)
- 简单但细节问题思考
- python log模块最简单的写法
- redis---RDB
- 数据结构练习——简单双链表
- iOS
- contos7.4 安装lump环境php7.2+mysql7.2+nginx(最全面,无误的)
- Android 设置全屏沉浸式透明状态栏的工具类(超级简单,两步实现)
- Selenium-异常:Element is not currently visible and so may not be interacted with的解决
- adb和adbd分析
- syslog架构
- idea多模块开发
- Mac OS 安装 与 卸载 JDK
- Email 控件Aspose.Email 12月新版17.12发布 | 附下载