使用NSURLProtocol和NSURLSession拦截UIWebView的HTTP请求(包括ajax请求)

时间:2023-01-24 11:01:05

问题:服务器端有一个网站需要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] == ) {
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]];

Demo地址