MARK: - 存在的问题
服务器端有一个网站需要AD认证,整站都开了Basic认证,包括图片,CSS等资源,我在HTTP请求头里面添加认证所需的用户名和密码,传递到服务器端可以认证通过。我在UIWebView的shouldStartLoadWithRequest代理方法中拦截WebView的请求,然后在请求的Header中添加认证所需的用户名和密码,然后使用NSURLSession重新发出HTTP的请求,这种方法可以解决大部分的网络请求,但是无法拦截到网页内部的ajax请求,所以所有的ajax请求都会失败,一旦遇到ajax请求,认证都会失败,并且网页会失去响应?
MARK: - 解决思路:
使用NSURLProtocol拦截UIWebView内部的所有请求,包括Ajax请求,在所有的请求头中添加认证所需的用户名和密码。
MARK: - 直接上代码:【创建工具类】
新建一个class: MTURLSessionProtocol 继承NSURLProtocol
#import <Foundation/Foundation.h>
@interface MTURLSessionProtocol : NSURLProtocol
@end
#import "MTURLSessionProtocol.h"
static NSString *const MTURLProtocolHandleKey = @"MTURLProtocolHandleKey";
@interface MTURLSessionProtocol()<NSURLSessionDelegate>
@property (atomic ,strong, readwrite) NSURLSessionDataTask *task; // 确保原子性,数据安全
@property (nonatomic, strong) NSURLSession *session;
@end
@implementation MTURLSessionProtocol
+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
// 只处理https请求
NSString *scheme = [[request URL] scheme];
if ([scheme caseInsensitiveCompare:@"http"] == NSOrderedSame ||
[scheme caseInsensitiveCompare:@"https"] == NSOrderedSame) {
NSLog(@"====>%@",request.URL);
// 防止无限循环
if ([NSURLProtocol propertyForKey:MTURLProtocolHandleKey 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:MTURLProtocolHandleKey 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;
}
#pragma mark - NSURLSessionDelegate
- (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);
}
- (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:MTURLProtocolHandleKey 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]];
}
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler{
NSURLCredential * credential;
assert(challenge != nil);
credential = nil;
NSLog(@"----received challenge----");
NSString *authenticationMethod = [[challenge protectionSpace] authenticationMethod];
if ([authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
NSLog(@"----server verify client----");
NSString *host = challenge.protectionSpace.host;
SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
BOOL validDomain = false;
NSMutableArray *polices = [NSMutableArray array];
if (validDomain) {
[polices addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)host)];
} else{
[polices addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
}
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)polices);
//pin mode for certificate
NSString *path = [[NSBundle mainBundle] pathForResource:@"client" ofType:@"cer"];
NSData *certData = [NSData dataWithContentsOfFile:path];
NSMutableArray *pinnedCerts = [NSMutableArray arrayWithObjects:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certData), nil];
SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCerts);
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
} else {
NSLog(@"----client verify server----");
SecIdentityRef identity = NULL;
SecTrustRef trust = NULL;
NSString *p12 = [[NSBundle mainBundle] pathForResource:@"client" ofType:@"p12"];
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:p12]) {
NSLog(@"client.p12 file not exist!");
} else{
NSData *pkcs12Data = [NSData dataWithContentsOfFile:p12];
if ([[self class] extractIdentity:&identity andTrust:&trust fromPKCS12Data:pkcs12Data]) {
SecCertificateRef certificate = NULL;
SecIdentityCopyCertificate(identity, &certificate);
const void *certs[] = {certificate};
CFArrayRef certArray = CFArrayCreate(kCFAllocatorDefault, certs, 1, NULL);
credential = [NSURLCredential credentialWithIdentity:identity certificates:(__bridge NSArray *)certArray persistence:NSURLCredentialPersistencePermanent];
completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
}
}
}
}
+ (BOOL)extractIdentity:(SecIdentityRef *)outIdentity andTrust:(SecTrustRef *)outTrust fromPKCS12Data:(NSData *)inPKCS12Data {
OSStatus securityErr = errSecSuccess;
NSDictionary *optionsDic = [NSDictionary dictionaryWithObject:@"123321" forKey:(__bridge id)kSecImportExportPassphrase];
CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
securityErr = SecPKCS12Import((__bridge CFDataRef)inPKCS12Data, (__bridge CFDictionaryRef)optionsDic, &items);
if (securityErr == errSecSuccess) {
CFDictionaryRef mineIdentAndTrust = CFArrayGetValueAtIndex(items, 0);
const void *tmpIdentity = NULL;
tmpIdentity = CFDictionaryGetValue(mineIdentAndTrust, kSecImportItemIdentity);
*outIdentity = (SecIdentityRef)tmpIdentity;
const void *tmpTrust = NULL;
tmpTrust = CFDictionaryGetValue(mineIdentAndTrust, kSecImportItemTrust);
*outTrust = (SecTrustRef)tmpTrust;
} else{
return false;
}
return true;
}
@end
为NSURLProtocol 新建一个分类WebKitSupport
#import <Foundation/Foundation.h>
@interface NSURLProtocol (WebKitSupport)
+ (void)wk_registerScheme:(NSString*)scheme;
+ (void)wk_unregisterScheme:(NSString*)scheme;
@end
#import "NSURLProtocol+WebKitSupport.h"
#import <WebKit/WebKit.h>
/**
* The functions below use some undocumented APIs, which may lead to rejection by Apple.
*/
FOUNDATION_STATIC_INLINE Class ContextControllerClass() {
static Class cls;
if (!cls) {
cls = [[[WKWebView new] valueForKey:@"browsingContextController"] class];
}
return cls;
}
FOUNDATION_STATIC_INLINE SEL RegisterSchemeSelector() {
return NSSelectorFromString(@"registerSchemeForCustomProtocol:");
}
FOUNDATION_STATIC_INLINE SEL UnregisterSchemeSelector() {
return NSSelectorFromString(@"unregisterSchemeForCustomProtocol:");
}
@implementation NSURLProtocol (WebKitSupport)
+ (void)wk_registerScheme:(NSString *)scheme {
Class cls = ContextControllerClass();
SEL sel = RegisterSchemeSelector();
if ([(id)cls respondsToSelector:sel]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[(id)cls performSelector:sel withObject:scheme];
#pragma clang diagnostic pop
}
}
+ (void)wk_unregisterScheme:(NSString *)scheme {
Class cls = ContextControllerClass();
SEL sel = UnregisterSchemeSelector();
if ([(id)cls respondsToSelector:sel]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[(id)cls performSelector:sel withObject:scheme];
#pragma clang diagnostic pop
}
}
@end
上面工具类已经创建完成,下面介绍使用方式
MARK: - 用法
UIWebView Https 双向验证
#import "UIWebViewController.h"
#import "MTURLSessionProtocol.h"
@interface UIWebViewController ()
@property (nonatomic, strong) UIWebView *webView;
@end
@implementation UIWebViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
_webView = [[UIWebView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:_webView];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:@"https://www.zykj188.com/v2/iframepage/index.html#/myPrivilege"]];
[self.webView loadRequest:request];
//注册网络请求拦截
[NSURLProtocol registerClass:[MTURLSessionProtocol class]];
}
- (void)dealloc {
//拦截整个App中所有的网络请求
//可以在ViewWillDisappear中添加取消网络拦截的代码
[NSURLProtocol unregisterClass:[MTURLSessionProtocol class]];
}
拦截整个App中所有的网络请求
直接在AppDelegate中的didFinishLaunchingWithOptions注册网络拦截代码
//注册Protocol
[NSURLProtocol registerClass:[RichURLSessionProtocol class]];
UIWebView Https 双向验证
#import "WKWebViewController.h"
#import <WebKit/WebKit.h>
#import "MTURLSessionProtocol.h"
#import "NSURLProtocol+WebKitSupport.h"
@interface WKWebViewController ()
@property (nonatomic, strong) WKWebView *webView;
@end
@implementation WKWebViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
// 比UIWebView多了注册scheme这一步
for (NSString *scheme in @[@"http", @"https"]) {
[NSURLProtocol wk_registerScheme:scheme];
}
_webView = [[WKWebView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:_webView];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:@"https://www.zykj188.com/v2/iframepage/index.html#/myPrivilege"]];
[self.webView loadRequest:request];
//注册网络请求拦截
[NSURLProtocol registerClass:[MTURLSessionProtocol class]];
}
- (void)dealloc {
[NSURLProtocol unregisterClass:[MTURLSessionProtocol class]];
}
补充:
双向验证需要一些相关的证书,我这里用的是client.cer、 client.p12证书,
这是参考别人的博客,纯属技术交流,如遇版权问题,请及时沟通
网友评论