问题:服务器端有一个网站需要AD认证,整站都开了Basic认证,包括图片,CSS等资源,我在HTTP请求头里面添加认证所需的用户名和密码,传递到服务器端可以认证通过。我在UIWebView的shouldStartLoadWithRequest代理方法中拦截WebView的请求,然后在请求的Header中添加认证所需的用户名和密码,然后使用NSURLSession重新发出HTTP的请求,这种方法可以解决大部分的网络请求,但是无法拦截到网页内部的ajax请求,所以所有的ajax请求都会失败,一旦遇到ajax请求,认证都会失败,并且网页会失去响应?
解决思路:使用NSURLProtocol拦截UIWebView内部的所有请求,包括Ajax请求,在所有的请求头中添加认证所需的用户名和密码。
注意:NSURLProtocol只能拦截UIWebView、NSURLConnection、NSURLSession和基于NSURLConnenction、NSURLSession实现的第三方框架(如AFNetworking)发出的网络请求,无法拦截WKWebview、CFNetwork以及基于CFNetwork实现的第三方框架(如MKNetworkit)发出的网络请求。 //update on 2017-02-28
下面提供一个完整的NSURLProtocol的实现类:
RichURLSessionProtocol.h
#import <Foundation/Foundation.h> @interface RichURLSessionProtocol : NSURLProtocol @end
RichURLSessionProtocol.m
#import "RichURLSessionProtocol.h" static NSString * const RichURLProtocolHandledKey = @"RichURLProtocolHandledKey"; @interface RichURLSessionProtocol()<NSURLSessionDelegate> @property (atomic,strong,readwrite) NSURLSessionDataTask *task; @property (nonatomic,strong) NSURLSession *session; @end @implementation RichURLSessionProtocol + (BOOL)canInitWithRequest:(NSURLRequest *)request { //只处理http和https请求 NSString *scheme = [[request URL] scheme]; if ( ([scheme caseInsensitiveCompare:@"http"] == NSOrderedSame || [scheme caseInsensitiveCompare:@"https"] == NSOrderedSame)) { // NSLog(@"====>%@",request.URL); //看看是否已经处理过了,防止无限循环 if ([NSURLProtocol propertyForKey:RichURLProtocolHandledKey inRequest:request]) { return NO; } return YES; } return NO; } + (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request { /** 可以在此处添加头等信息 */ NSMutableURLRequest *mutableReqeust = [request mutableCopy]; return mutableReqeust; } - (void)startLoading { NSMutableURLRequest *mutableReqeust = [[self request] mutableCopy]; //打标签,防止无限循环 [NSURLProtocol setProperty:@YES forKey:RichURLProtocolHandledKey inRequest:mutableReqeust]; NSURLSessionConfiguration *configure = [NSURLSessionConfiguration defaultSessionConfiguration]; NSOperationQueue *queue = [[NSOperationQueue alloc] init]; self.session = [NSURLSession sessionWithConfiguration:configure delegate:self delegateQueue:queue]; self.task = [self.session dataTaskWithRequest:mutableReqeust]; [self.task resume]; } - (void)stopLoading { [self.session invalidateAndCancel]; self.session = nil; } - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { if (error != nil) { [self.client URLProtocol:self didFailWithError:error]; }else { [self.client URLProtocolDidFinishLoading:self]; } } - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler { [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; completionHandler(NSURLSessionResponseAllow); } - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { [self.client URLProtocol:self didLoadData:data]; } - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask willCacheResponse:(NSCachedURLResponse *)proposedResponse completionHandler:(void (^)(NSCachedURLResponse * _Nullable))completionHandler { completionHandler(proposedResponse); } //TODO: 重定向 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)newRequest completionHandler:(void (^)(NSURLRequest *))completionHandler { NSMutableURLRequest* redirectRequest; redirectRequest = [newRequest mutableCopy]; [[self class] removePropertyForKey:RichURLProtocolHandledKey inRequest:redirectRequest]; [[self client] URLProtocol:self wasRedirectedToRequest:redirectRequest redirectResponse:response]; [self.task cancel]; [[self client] URLProtocol:self didFailWithError:[NSError errorWithDomain:NSCocoaErrorDomain code:NSUserCancelledError userInfo:nil]]; } - (instancetype)initWithRequest:(NSURLRequest *)request cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id<NSURLProtocolClient>)client { NSMutableURLRequest* redirectRequest; redirectRequest = [request mutableCopy]; //添加认证信息 NSString *authString = [[[NSString stringWithFormat:@"%@:%@", kGlobal.userInfo.sAccount, kGlobal.userInfo.sPassword] dataUsingEncoding:NSUTF8StringEncoding] base64EncodedString]; authString = [NSString stringWithFormat: @"Basic %@", authString]; [redirectRequest setValue:authString forHTTPHeaderField:@"Authorization"]; NSLog(@"拦截的请求:%@",request.URL.absoluteString); self = [super initWithRequest:redirectRequest cachedResponse:cachedResponse client:client]; if (self) { // Some stuff } return self; } - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler{ NSLog(@"自定义Protocol开始认证..."); NSString *authMethod = [[challenge protectionSpace] authenticationMethod]; NSLog(@"%@认证...",authMethod); if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { NSURLCredential *card = [[NSURLCredential alloc] initWithTrust:challenge.protectionSpace.serverTrust]; completionHandler(NSURLSessionAuthChallengeUseCredential,card); } if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodNTLM]) { if ([challenge previousFailureCount] == 0) { NSURLCredential *credential = [NSURLCredential credentialWithUser:kGlobal.userInfo.sAccount password:kGlobal.userInfo.sPassword persistence:NSURLCredentialPersistenceForSession]; [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge]; completionHandler(NSURLSessionAuthChallengeUseCredential,credential); }else{ completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge,nil); } } NSLog(@"自定义Protocol认证结束"); } @end
使用自定义NSURLProtocol类的方法:
1.在单个的UIViewController中使用
//导入自定义NSURLProtocol类
#import "RichURLSessionProtocol.h"
//在ViewDidLoad中添加拦截网络请求的代码
//注册网络请求拦截 [NSURLProtocol registerClass:[RichURLSessionProtocol class]];
//在ViewWillDisappear中添加取消网络拦截的代码
//取消注册网络请求拦截 [NSURLProtocol unregisterClass:[RichURLSessionProtocol class]];
2.拦截整个App中所有的网络请求
//直接在AppDelegate中的didFinishLaunchingWithOptions注册网络拦截代码
//注册Protocol [NSURLProtocol registerClass:[RichURLSessionProtocol class]];