什么是DNS劫持
DNS劫持就是通過(guò)劫持了DNS服務(wù)器,通過(guò)某些手段取得某域名的解析記錄控制權(quán)钞啸,進(jìn)而修改此域名的解析結(jié)果,導(dǎo)致對(duì)該域名的訪問(wèn)由原IP地址轉(zhuǎn)入到修改后的指定IP,其結(jié)果就是對(duì)特定的網(wǎng)址不能訪問(wèn)或訪問(wèn)的是假網(wǎng)址顿肺,從而實(shí)現(xiàn)竊取資料或者破壞原有正常服務(wù)的目的遮晚。
常見(jiàn)的DNS劫持現(xiàn)象網(wǎng)絡(luò)運(yùn)營(yíng)商向網(wǎng)頁(yè)中注入了Javascript代碼性昭,甚至直接將我們的網(wǎng)頁(yè)請(qǐng)求轉(zhuǎn)發(fā)到他們自己的廣告頁(yè)面或者通過(guò)自己的DNS服務(wù)器將用戶請(qǐng)求的域名指向到非法地址
如何解決DNS被劫持
全站使用HTTPS協(xié)議,或者采用HttpDNS县遣,通過(guò)HTTP向自建的DNS服務(wù)器或者安全的DNS服務(wù)器發(fā)送域名解析請(qǐng)求糜颠,然后根據(jù)解析結(jié)果設(shè)置客戶端的Host指向,從而繞過(guò)網(wǎng)絡(luò)運(yùn)營(yíng)商的DNS解析服務(wù)萧求。
本文的解決方案
客戶端對(duì)WebView的html請(qǐng)求進(jìn)行DNS解析其兴。優(yōu)先使用阿里、騰訊夸政、114等公共安全的DNS服務(wù)器解析客戶端的所有指定域名的http請(qǐng)求元旬。相對(duì)來(lái)講我們自己的服務(wù)域名變化較少,對(duì)此我們做了一個(gè)白名單,把凡是訪問(wèn)包含我們公司域名的請(qǐng)求都必須通過(guò)白名單的解析和DNS驗(yàn)證匀归。從而杜絕被劫持的情況出現(xiàn)坑资,這時(shí)候NSURLProtocol就派上用場(chǎng)了。
NSURLProtocol
這是一個(gè)抽象類穆端,所以在oc中只能通過(guò)繼承來(lái)重寫父類的方法袱贮。
@interface XRKURLProtocol : NSURLProtocol
@end
然后在AppDelegate的 application:didFinishLaunchingWithOptions: 方法或者程序首次請(qǐng)求網(wǎng)絡(luò)數(shù)據(jù)之前去注冊(cè)這個(gè)NSURLProtocol的子類
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[NSURLProtocol registerClass:[XRKURLProtocol class]];
}
注冊(cè)了自定義的urlProtocol子類后,之后每一個(gè)http請(qǐng)求都會(huì)先經(jīng)過(guò)該類過(guò)濾并且通過(guò)+canInitWithRequest:
這個(gè)方法返回一個(gè)布爾值告訴系統(tǒng)該請(qǐng)求是否需要處理体啰,返回Yes才能進(jìn)行后續(xù)處理攒巍。
+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
if ([NSURLProtocol propertyForKey:URLProtocolHandledKey inRequest:request]) {
return NO;
}
//http和https都會(huì)出現(xiàn)dns劫持情況,都需要處理
NSString *scheme = [[request URL] scheme];
if (([scheme caseInsensitiveCompare:@"http"] == NSOrderedSame)) {
// 判斷請(qǐng)求是否為白名單
NSArray *whiteLists = [XRKConfigManager sharedManager].whiteList;
if (whiteLists && [whiteLists isKindOfClass:[NSArray class]]) {
for (NSString *url in whiteLists) {
if (request.URL.host && [request.URL.host hasSuffix:url]) {
return YES;
}
}
}
}
return NO;
}
+canonicalRequestForRequest:
這個(gè)父類的抽象方法子類必須實(shí)現(xiàn)狡赐。
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
return request;
}
以下是官方對(duì)這個(gè)方法的解釋窑业。當(dāng)我們想對(duì)某個(gè)請(qǐng)求添加請(qǐng)求頭或者返回新的請(qǐng)求時(shí),可以在這個(gè)方法里自定義然后返回枕屉,一般情況下直接返回參數(shù)里的NSURLRequest實(shí)例即可常柄。
It is up to each concrete protocol implementation to define what “canonical” means. A protocol should guarantee that the same input request always yields the same canonical form.
+requestIsCacheEquivalent:toRquest:
這個(gè)方法能夠判斷當(dāng)攔截URL相同時(shí)是否使用緩存數(shù)據(jù),以下例子是直接返回父類實(shí)現(xiàn)搀擂。
+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b {
return [super requestIsCacheEquivalent:a toRequest:b];
}
-startLoading
和-stopLoading
兩個(gè)方法分別告訴NSURLProtocol實(shí)現(xiàn)開(kāi)始和取消請(qǐng)求的處理西潘。
- (void)startLoading {
NSMutableURLRequest *mutableReqeust = [[self request] mutableCopy];
//打標(biāo)簽,防止無(wú)限循環(huán)
[NSURLProtocol setProperty:@YES forKey:URLProtocolHandledKey inRequest:mutableReqeust];
// dns解析
NSMutableURLRequest *request = [self.class replaceHostInRequset:mutableReqeust];
self.connection = [NSURLConnection connectionWithRequest:request delegate:self];
}
+ (NSMutableURLRequest *)replaceHostInRequset:(NSMutableURLRequest *)request {
if ([request.URL host].length == 0) {
return request;
}
NSString *originUrlString = [request.URL absoluteString];
NSString *originHostString = [request.URL host];
NSRange hostRange = [originUrlString rangeOfString:originHostString];
if (hostRange.location == NSNotFound) {
return request;
}
//用HappyDNS 替換host
NSMutableArray *array = [NSMutableArray array];
/// 第一dns解析為114哨颂,第二解析才是系統(tǒng)dns
[array addObject:[[QNResolver alloc] initWithAddress:@"114.114.115.115"]];
[array addObject:[QNResolver systemResolver]];
QNDnsManager *dnsManager = [[QNDnsManager alloc] init:array networkInfo:[QNNetworkInfo normal]];
NSArray *queryArray = [dnsManager query:originHostString];
if (queryArray && queryArray.count > 0) {
NSString *ip = queryArray[0];
if (ip && ip.length) {
// 替換host
NSString *urlString = [originUrlString stringByReplacingCharactersInRange:hostRange withString:ip];
NSURL *url = [NSURL URLWithString:urlString];
request.URL = url;
[request setValue:originHostString forHTTPHeaderField:@"Host"];
}
}
return request;
}
- (void)stopLoading {
[self.connection cancel];
}
由于我們?cè)?code>-startLoading中新建了一個(gè)NSURLConnection實(shí)例喷市,因此要實(shí)現(xiàn)NSURLConnectionDelegate
的委托方法。
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
[self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[self.client URLProtocol:self didLoadData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
[self.client URLProtocolDidFinishLoading:self];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
[self.client URLProtocol:self didFailWithError:error];
}
至此威恼,通過(guò)NSURLProtocol和QNDnsManager(七牛DNS解析開(kāi)源庫(kù))可以解決DNS劫持問(wèn)題品姓。但是NSURLProtocol還有更多的用途,以下是本文第二個(gè)內(nèi)容:webView上web請(qǐng)求的資源本地化箫措。
Web資源本地化
這里只舉一個(gè)簡(jiǎn)單的示例腹备,同樣是在上述NSURLProtocol的子類的-startLoading
方法里
- (void)startLoading {
NSMutableURLRequest *mutableReqeust = [[self request] mutableCopy];
// 處理MIME type
NSString *mimeType = nil;
mutableReqeust = [self.class replaceLocalSource:mutableReqeust];
NSString *pathComponent = mutableReqeust.URL.absoluteString.lastPathComponent;
if ([pathComponent hasSuffix:@"js"]) {
mimeType = @"text/javascript";
} else if ([pathComponent hasSuffix:@"css"]) {
mimeType = @"text/css";
}
if (mimeType) {
NSData *data = [NSData dataWithContentsOfFile:mutableReqeust.URL.absoluteString];
NSURLResponse *response = [[NSURLResponse alloc] initWithURL:[[self request] URL]
MIMEType:mimeType
expectedContentLength:[data length]
textEncodingName:@"UTF8"];
[[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
[[self client] URLProtocol:self didLoadData:data];
[[self client] URLProtocolDidFinishLoading:self];
}
}
#pragma mark - 判斷是否是本地資源
+ (BOOL)canReplaceLocalSource:(NSURLRequest *)request {
NSString *absoluteString = request.URL.absoluteString;
for (NSString *localSourceurl in [self localSourceArray]) {
if ([absoluteString isEqualToString:localSourceurl]) {
return YES;
}
}
return NO;
}