美文网首页
webView https双向验证

webView https双向验证

作者: 那是一阵清风_徐来 | 来源:发表于2018-11-02 16:19 被阅读165次
    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证书,
    这是参考别人的博客,纯属技术交流,如遇版权问题,请及时沟通

    相关文章

      网友评论

          本文标题:webView https双向验证

          本文链接:https://www.haomeiwen.com/subject/igudiftx.html