转载地址:https://blog.csdn.net/icefishlily/article/details/78863305
HTTPDNS SDK手册链接 https://help.aliyun.com/document_detail/30141.html?spm=5176.doc30113.6.577.e8LhPM
https://help.aliyun.com/product/30100.html?spm=a2c4g.11186623.6.540.54136c1ejgXrdm
https://www.jianshu.com/p/cef92a5aea01
首先说明一下,要解决的问题: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可以接管应用程序所发出的所有请求.NSURLProtocol是抽象类,需要创建一个子类才能使用在子类中重写必要的方法,来实现响应的接管功能:
(1)
第一步:判断哪些请求需要拦截,需要返回是;不需要返回NO
+(BOOL)canInitWithRequest:(NSURLRequest *)请求
(2)
第二步:对拦截的请求进行处理,修改主机,添加cookie等
+(NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)请求
(3)
第三步:建立NSURLSesion对象,并发起请求
- (void)startLoading
(4)
第四步:停止请求
- (void)stopLoading
(5)
第五步:NSURLSessionDelegate中,完成
请求返回数据的回吐,即将请求返回的数据回送给客户端(本来请求的发起者,如UIWebView)
https证书的校验
拦截流程图:
拦截之后的处理流程图:
//
// CustomURLProtocol.h
// iphone-pay
//
// Created by HLYUE on 2017/11/7.
// Copyright © 2017年 RHJX Inc. All rights reserved.
//
#import
@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
static NSString * const URLProtocolHandledKey = @"URLProtocolHandledKey";
@interface CustomURLProtocol ()
@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)应用程序:(UIApplication *)应用程序didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
中调用
对于iOS HTTPDNS的SNI场景说明:HTTPDNS的官网并没有成熟的SNI场景解决方案(针对iOS,安卓是有的)。所以我并没有支持SNI场景。也希望其他朋友们珍重。如有大能使用了,请不吝赐教,谢谢!