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

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

Demo地址

原文地址:https://www.cnblogs.com/wobuyayi/p/6283599.html