UIWebView被WKWebView替换后在iOS12(不包含12)以下https双向认证失败的解决历程总结

 

在日版iPhone5,10.3.3 系统上,有一些遗留问题,属于另一个问题。

在这篇里解决了 世界之大无奇不有之iOS网络请求中HTTPBody内的键值对顺序会导致请求失败

 原生AF请求,第一个和登录(未改动)旧版UIWebview双向认证新版WKWebView双向认证
iPhone5日版10.3.3真机报错(改完顺序之后正常)正常改前报错改后正常
10.3.1 - 11.4模拟器(无10.3.3)正常正常改前报错改后正常
12.4.1以上真机及模拟器正常正常改前正常改后正常

 

在UIWebView时代,UIWebView的delegate没有处理双向认证的方法,需要在shouldStartLoadWithRequest方法中将请求拦截使用NSURLConnection来发起这个请求,NSURLConnection收到响应didReceiveResponse后取消请求,再将请求使用webview重新加载。

在WKWebView时代系统提供了处理挑战(双向认证)的方法,

/*! @abstract Invoked when the web view needs to respond to an authentication challenge.
 @param webView The web view that received the authentication challenge.
 @param challenge The authentication challenge.
 @param completionHandler The completion handler you must invoke to respond to the challenge. The
 disposition argument is one of the constants of the enumerated type
 NSURLSessionAuthChallengeDisposition. When disposition is NSURLSessionAuthChallengeUseCredential,
 the credential argument is the credential to use, or nil to indicate continuing without a
 credential.
 @discussion If you do not implement this method, the web view will respond to the authentication challenge with the NSURLSessionAuthChallengeRejectProtectionSpace disposition.
 */
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler;

 

一般情况在这个方法里面直接处理就可以,可是ios12及以上没问题,以下都有问题。

 

猜想:是什么原因导致的呢?

1.从bundle读取文件转成NSData发现ios10系统鼠标放上面是空的,但打印有值。多次尝试不是这个问题

2.从p12文件读取私钥的方法是不是在ios10和12返回值不同。但怎么看呢,打印数据没有直接显示的。

那就抓包试试看,直接不设代理抓取网卡上的底层包。参考  SSL/TSL双向认证过程与Wireshark抓包分析

复习了一下三次握手

借图

不是这个问题

3.ios10原生AFNet部分接口认证通过,部分不通过。多次尝试,说明上面的方法没问题

 

 

 

 

如果在wk使用UIWebView的那一套,NSURLConnection来拦截一下,didReceiveResponse后取消请求,wk继续加载,发现不可行。

那didReceiveResponse后不取消,一直请求完成,然后wk直接加载NSURLConnection返回的数据,这样行不行呢,终于取得了进展,认证可通过,但发现ajax发的请求会有问题。

 

上面几种正规方法都试过了,不行那只能私有api了,

而且没有解决问题就不放中间代码了。

 

先使用NSURLProtocol把WKWebView的所有请求拦截,在NSURLProtocol实现双向认证,发现效果还可以,就是ajax的请求参数没了,然后找到这么一个库“WKHookAjax”,解决了丢参问题,问题解决。

 

下面是最终解决问题的代码

1.页面创建后拦截请求的代码

    [NSURLProtocol registerClass:[MyConnectionURLProtocol class]];
    Class cls = NSClassFromString(@"WKBrowsingContextController");
    SEL sel = NSSelectorFromString(@"registerSchemeForCustomProtocol:");
    if ([(id)cls respondsToSelector:sel]) {
        // 注册http(s) scheme, 把 http和https请求交给 NSURLProtocol处理
        [(id)cls performSelector:sel withObject:@"http"];
        [(id)cls performSelector:sel withObject:@"https"];

    }

2.MyConnectionURLProtocol的代码是

.h

#import <Foundation/Foundation.h>

@interface MyConnectionURLProtocol : NSURLProtocol

@end
.m


#import "MyConnectionURLProtocol.h"

#define protocolKey @"ConnectionProtocolKey"

@interface MyConnectionURLProtocol () <NSURLConnectionDataDelegate>

@property (nonatomic, strong) NSURLConnection * connection;
@property (nonatomic, assign) NSStringEncoding stringEncoding ;
@end

@implementation MyConnectionURLProtocol

//
//返回YES表示要拦截处理 走的第一个方法
//
+ (BOOL)canInitWithRequest:(NSMutableURLRequest *)request {
    NSString * url = request.URL.absoluteString;
    NSMutableURLRequest *mutableReq = [request mutableCopy];
    NSMutableDictionary *headers = [mutableReq.allHTTPHeaderFields mutableCopy];
    //防止已拦截的请求重复请求
    if ([headers objectForKey:@"Key1"]) {
        return NO;
    }
    NSLog(@"URL||||||||||   %@",request.URL);
    NSLog(@"HTTPMethod||||||||||   %@",request.HTTPMethod);
    NSLog(@"HTTPBody||||||||||  %@",[[NSString alloc]initWithData:request.HTTPBody encoding:NSUTF8StringEncoding] );
    NSLog(@"Header||||||||||   %@",request.allHTTPHeaderFields);
    // 如果url已http或https开头,则进行拦截处理,否则不处理
    if ([url hasPrefix:@"http"] || [url hasPrefix:@"https"]) {
        return YES;
    }
    return NO;
} 
//
//修改请求头 防止死循环
//
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
    // 修改了请求的头部信息
    NSMutableURLRequest *mutableRequest = [request mutableCopy];
    [mutableRequest setValue:@"AAAA" forHTTPHeaderField:@"Key1"];
    return mutableRequest;
}

//
//开始加载
//
- (void)startLoading{
    NSMutableURLRequest *request = [self.request mutableCopy];
    //
    //不是需要更改的页面 则通过NSURLConnection去请求
    //
    self.connection = [NSURLConnection connectionWithRequest:request delegate:self];
}
//
//取消请求
//
- (void)stopLoading {
    [self.connection cancel];
}

//
//#pragma mark - NSURLConnectionDelegate
//
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    
    [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    NSString *str = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];;
    if (str.length < 1) {
        CFStringEncoding gbkEncoding =(unsigned int) CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGB_18030_2000);
        str =[[NSString alloc]initWithData:data encoding:gbkEncoding];
    }
    //
    //打印h5的代码
    //
    NSLog(@"%@ %@",connection,str);
    [self.client URLProtocol:self didLoadData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    [self.client URLProtocolDidFinishLoading:self];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    [self.client URLProtocol:self didFailWithError:error];
}
//
//处理认证
//
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
    
    NSURLCredential * credential;
    assert(challenge != nil);
    credential = nil;
 
    
    NSString *authenticationMethod = [[challenge protectionSpace] authenticationMethod];
    if ([authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
        
        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);

        NSString *path = [[NSBundle mainBundle] pathForResource:@"XXXXXXX" 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];
      
        
    } else {
       
        SecIdentityRef identity = NULL;
        SecTrustRef trust = NULL;
        NSString *p12 = [[NSBundle mainBundle] pathForResource:@"xxxxxxx" ofType:@"p12"];
        NSFileManager *fileManager = [NSFileManager defaultManager];
        if (![fileManager fileExistsAtPath:p12]) {

        }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];
            }
        }
    }
   
    [challenge.sender useCredential:credential forAuthenticationChallenge:challenge];
}

+ (BOOL)extractIdentity:(SecIdentityRef *)outIdentity andTrust:(SecTrustRef *)outTrust fromPKCS12Data:(NSData *)inPKCS12Data {
    
    OSStatus securityErr = errSecSuccess;

    NSDictionary *optionsDic = [NSDictionary dictionaryWithObject:@"XXXXXXXXX" 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

3.hook ajax  这里就不放WKHookAjax的下载地址了

    WKUserContentController *wkUController = [[WKUserContentController alloc] init];
    WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
    config.userContentController = wkUController;
//注意 一定要写在上面那句的下面
    [config.userContentController imy_installHookAjax];

 

4.如果有不需要认证的,不需要hook ajax的 ,可以在适当的地方取消拦截 取消hook

 

 

疑问:NSURLConnection和NSURLSession和WKWebView这三个处理双向认证有什么区别,为什么只有NSURLConnection才是最稳定的。

那部分机型原生请求出错是不是可以把NSURLSession换成NSURLConnection呢?

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值