使用NSURLProtocol和NSURLSession攔截UIWebView的HTTP請求(包括ajax請求)

問題:服務器端有一個網站須要AD認證,整站都開了Basic認證,包括圖片,CSS等資源,我在HTTP請求頭裏面添加認證所需的用戶名和密碼,傳遞到服務器端能夠認證經過。我在UIWebView的shouldStartLoadWithRequest代理方法中攔截WebView的請求,而後在請求的Header中添加認證所需的用戶名和密碼,而後使用NSURLSession從新發出HTTP的請求,這種方法能夠解決大部分的網絡請求,可是沒法攔截到網頁內部的ajax請求,因此全部的ajax請求都會失敗,一旦遇到ajax請求,認證都會失敗,而且網頁會失去響應?git

解決思路:使用NSURLProtocol攔截UIWebView內部的全部請求,包括Ajax請求,在全部的請求頭中添加認證所需的用戶名和密碼。github

注意:NSURLProtocol只能攔截UIWebView、NSURLConnection、NSURLSession和基於NSURLConnenction、NSURLSession實現的第三方框架(如AFNetworking)發出的網絡請求,沒法攔截WKWebview、CFNetwork以及基於CFNetwork實現的第三方框架(如MKNetworkit)發出的網絡請求。 //update on 2017-02-28ajax

下面提供一個完整的NSURLProtocol的實現類:服務器

RichURLSessionProtocol.h網絡

#import <Foundation/Foundation.h>

@interface RichURLSessionProtocol : NSURLProtocol

@end

RichURLSessionProtocol.msession

#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類的方法:app

1.在單個的UIViewController中使用框架

//導入自定義NSURLProtocol類網站

#import "RichURLSessionProtocol.h"

//在ViewDidLoad中添加攔截網絡請求的代碼atom

//註冊網絡請求攔截
[NSURLProtocol registerClass:[RichURLSessionProtocol class]];

//在ViewWillDisappear中添加取消網絡攔截的代碼

//取消註冊網絡請求攔截
[NSURLProtocol unregisterClass:[RichURLSessionProtocol class]];

2.攔截整個App中全部的網絡請求

//直接在AppDelegate中的didFinishLaunchingWithOptions註冊網絡攔截代碼

//註冊Protocol
[NSURLProtocol registerClass:[RichURLSessionProtocol class]];

 

Demo地址

相關文章
相關標籤/搜索