原文:http://www.cnblogs.com/wobuyayi/p/6283599.html
【问题】
服务器端有一个网站需要 AD 认证,整站都开了 Basic 认证,包括图片,CSS 等资源,我在 HTTP 请求头里面添加认证所需的用户名和密码,传递到服务器端可以认证通过。我在 UIWebView
的 shouldStartLoadWithRequest
代理方法中拦截 UIWebView
的请求,然后在请求的 Header 中添加认证所需的用户名和密码,然后使用 NSURLSession
重新发出 HTTP 的请求,这种方法可以解决大部分的网络请求,但是无法拦截到网页内部的 Ajax 请求,所以所有的 Ajax 请求都会失败,一旦遇到 Ajax 请求,认证都会失败,并且网页会失去响应?
【解决思路】
使用 NSURLProtocol
拦截 UIWebView
内部的所有请求,包括 Ajax 请求,在所有的请求头中添加认证所需的用户名和密码。
注意:NSURLProtocol
只能拦截 UIWebView
、NSURLConnection
、NSURLSession
和基于 NSURLConnenction
、NSURLSession
实现的第三方框架(如:AFNetworking)发出的网络请求,无法拦截 WKWebview
、CFNetwork
以及基于 CFNetwork
实现的第三方框架(如:MKNetworkit)出的网络请求。
一、下面提供一个完整的 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
四、参考资料
《iOS 开发中使用 NSURLProtocol 拦截 HTTP 请求》
(完)
网友评论