美文网首页iOS 技术分享
iOS - webView加载https

iOS - webView加载https

作者: Joh蜗牛 | 来源:发表于2021-05-08 15:14 被阅读0次

    前言

    APP项目中,嵌入了几个h5页面,页面中包含了使用【WebViewJavascriptBridge】与h5页面的交互,之前一切正常。
    最近,项目的h5页面经过了SSL证书加密,变成了https请求,页面中数据不能正常加载了。页面布局可以正常展示,数据请求失败,如下:

    错误页面

    排查问题

    1.定位问题

    加密之后,iOS及安卓都出现了上述问题,安卓端经过处理后,可以正常展示(询问过后,是安卓端在webView页面做了允许http及https混合加载处理,便可正常展示了)

    由于安卓端处理好了,我这边便以为是iOS端内部的问题,于是开始在网上搜索【UIWebView/WKWebView加载https】的相关文章。

    2.查找解决办法

    网上给出了很多种解决方法,针对的问题大多都是【由于服务器证书无效,即证书不受信任】,几种解决问题的方法如下:

    <1>方法一(WKWebView):

    描述:因公司域名更换https,因而造成在wkwebView中某些网址打不开,查看错误是因为服务器证书无效,实际就是不受信任;

    解决办法:在plist文件中设置
    Allow Arbitrary Loads in Web Content 置为 YES,
    假如有设置NSAllowsArbitraryLoads 为 YES,可不用设置上面;

    实现wkwebView下面的代理方法,就可解决 :

    • (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler{ if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { NSURLCredential *card = [[NSURLCredential alloc]initWithTrust:challenge.protectionSpace.serverTrust]; completionHandler(NSURLSessionAuthChallengeUseCredential,card); }
      }

    使用此方法后,问题未解决,继续找...

    <2>方法二(UIWebView):

    https使用超文本安全传输协议,即超文本传输协议(HTTP)和SSL/TLS的组合,用以提供加密通讯及对网络服务器身份的鉴定。当我们的服务器使用自我签名证书时,而UIWebView不允许使用自签名证书,所以导致加载失败。我们可以使用NSURLConnection通过它的代理canAuthenticateAgainstProtectionSpace可以允许这种情况,从而通过它进行认证。

    解决方法:

    #import <UIKit/UIKit.h>
    
    #import "MyObject.h"
    
    @interface ViewController :UIViewController<UIWebViewDelegate,NSURLConnectionDelegate>
    
    {
    
       UIWebView *_web;
    
        NSURLConnection *_urlConnection;
    
        NSURLRequest *_request;
    
        BOOL _authenticated;
    
    }
    
    @end
    
    
    
    
    
    @interfaceViewController ()
    
    
    
    @end
    
    
    
    @implementation ViewController
    
    @synthesize myobject;
    
    - (void)viewDidLoad
    
    {
    
        [superviewDidLoad];
    
        _web = [[UIWebViewalloc] initWithFrame:CGRectMake(0,0,768,1024)];
    
        _web.delegate =self;
    
        _web.autoresizingMask =UIViewAutoresizingFlexibleWidth |UIViewAutoresizingFlexibleHeight;
    
        [self.viewaddSubview:_web];
    
        NSURL* _loadingURL =[NSURLURLWithString:@"https://m.facebook.com/dialog/feed?link=http%3A%2F%2Fwww.facebook.com%2FeNGage.OnePub&description=Werwr4w532545245&access_token=BAAC117qbwhQBAFtqiZAmWH8hOtkJLOcC0OTgmYJxX5IybPlPp2ozoZBewaJXBtOagJ5bJItZCZBJ8o8Sal0c52Tt4h2XbPf2f5osEo1ZB2lQh0hF969zeOpoPu1BsS5Hgtr00U55gYb7ZABsvcFohG&caption=Page%201&name=eNGage&picture=http%3A%2F%2Fc1345842.cdn.cloudfiles.rackspacecloud.com%2Fassets%2Fapps%2Ficons%2F001%2F002%2F707%2Foriginal.png%3F1324531970&actions=%5B%7B%22name%22%3A%22Find%20out%20more%20about%20eNGage%22%2C%22link%22%3A%22http%3A%2F%2Fwww.facebook.com%2FeNGage.OnePub%22%7D%5D&app_id=199938153366036&redirect_uri=fbconnect%3A%2F%2Fsuccess&sdk=2&display=touch"];
    
        //_loadingURL=[NSURLRequest setAllowsAnyHTTPSCertificate:YES forHost:[_loadingURL host]];
    
        _request = [NSMutableURLRequestrequestWithURL:_loadingURL];
    
        [_webloadRequest:_request];
    
    // Do any additional setup after loading the view, typically from a nib.
    
    //        UIImageView *uimage=[[UIImageView alloc] initWithImage:[UIImage imageNamed:@"image27.png"]];
    
    //    [self.view addSubview:uimage];
    
    //    MyObject *object=[[MyObject alloc]init];
    
    //    MyObject *object1=[object retain];
    
        //self.myobject=object;
    
        //myobject = object;
    
        //NSLog(@"******retainCount:%d",[myobject retainCount]);
    
        //NSLog(@"******self.retainCount:%d",[self.myobject retainCount]);
    
        //[object release];
    
    }
    
    
    
    - (void)didReceiveMemoryWarning
    
    {
    
        [superdidReceiveMemoryWarning];
    
        // Dispose of any resources that can be recreated.
    
    }
    
    #pragma mark - Webview delegate
    
    
    
    // Note: This method is particularly important. As the server is using a self signed certificate,
    
    // we cannot use just UIWebView - as it doesn't allow for using self-certs. Instead, we stop the
    
    // request in this method below, create an NSURLConnection (which can allow self-certs via the delegate methods
    
    // which UIWebView does not have), authenticate using NSURLConnection, then use another UIWebView to complete
    
    // the loading and viewing of the page. See connection:didReceiveAuthenticationChallenge to see how this works.
    
    - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
    
    {
    
        NSLog(@"Did start loading: %@ auth:%d", [[requestURL]absoluteString],_authenticated);
    
        
    
        if (!_authenticated) {
    
            _authenticated =NO;
    
            
    
            _urlConnection = [[NSURLConnectionalloc] initWithRequest:_requestdelegate:self];
    
            
    
            [_urlConnectionstart];
    
            
    
            returnNO;
    
        }
    
        
    
        returnYES;
    
    }
    
    
    
    - (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
    
        // 102 == WebKitErrorFrameLoadInterruptedByPolicyChange
    
       NSLog(@"***********error:%@,errorcode=%d,errormessage:%@",error.domain,error.code,error.description);
    
        if (!([error.domainisEqualToString:@"WebKitErrorDomain"] && error.code ==102)) {
    
            //[self dismissWithError:error animated:YES];
    
        }
    
    }
    
    
    
    #pragma mark - NURLConnection delegate
    
    
    
    - (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
    
    {
    
        NSLog(@"WebController Got auth challange via NSURLConnection");
    
        
    
        if ([challengepreviousFailureCount] ==0)
    
        {
    
            _authenticated =YES;
    
            
    
            NSURLCredential *credential = [NSURLCredentialcredentialForTrust:challenge.protectionSpace.serverTrust];
    
            
    
            [challenge.senderuseCredential:credentialforAuthenticationChallenge:challenge];
    
            
    
        } else
    
        {
    
            [[challenge sender]cancelAuthenticationChallenge:challenge];
    
        }
    
    }
    
    
    
    - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse*)response;
    
    {
    
        NSLog(@"WebController received response via NSURLConnection");
    
        
    
        // remake a webview call now that authentication has passed ok.
    
        _authenticated =YES;
    
        [_webloadRequest:_request];
    
        
    
        // Cancel the URL connection otherwise we double up (webview + url connection, same url = no good!)
    
        [_urlConnectioncancel];
    
    }
    
    
    
    // We use this method is to accept an untrusted site which unfortunately we need to do, as our PVM servers are self signed.
    
    - (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
    
    {
    
        return[protectionSpace.authenticationMethodisEqualToString:NSURLAuthenticationMethodServerTrust];
    
    }
    
    @end
    
    /*************************************************************other****************************************************/
    
    BOOL _Authenticated;
    NSURLRequest *_FailedRequest;
    
    #pragma UIWebViewDelegate
    
    -(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request   navigationType:(UIWebViewNavigationType)navigationType {
        BOOL result = _Authenticated;
        if (!_Authenticated) {
            _FailedRequest = request;
            NSURLConnection *urlConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
            [urlConnection start];
        }
        return result;
    }
    
    #pragma NSURLConnectionDelegate
    
    -(void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
        if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
            NSURL* baseURL = [NSURL URLWithString:@"your url"];
            if ([challenge.protectionSpace.host isEqualToString:baseURL.host]) {
                NSLog(@"trusting connection to host %@", challenge.protectionSpace.host);
                [challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
            } else
                NSLog(@"Not trusting connection to host %@", challenge.protectionSpace.host);
        }
        [challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
    }
    
    -(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)pResponse {
    _Authenticated = YES;
        [connection cancel];
        [self.webView loadRequest:_FailedRequest];
    }
    
    
    
    - (void)viewDidLoad{
       [super viewDidLoad];
    
        NSURL *url = [NSURL URLWithString:@"your url"];
        NSURLRequest *requestURL = [NSURLRequest requestWithURL:url];
        [self.webView loadRequest:requestURL];
    
    // Do any additional setup after loading the view.
    }
    /*************************************************************other****************************************************/
    
    
    
    #pragma mark - Webview delegate
    
    // Note: This method is particularly important. As the server is using a self signed certificate,
    // we cannot use just UIWebView - as it doesn't allow for using self-certs. Instead, we stop the
    // request in this method below, create an NSURLConnection (which can allow self-certs via the delegate methods
    // which UIWebView does not have), authenticate using NSURLConnection, then use another UIWebView to complete
    // the loading and viewing of the page. See connection:didReceiveAuthenticationChallenge to see how this works.
    - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
    {
        NSLog(@"Did start loading: %@ auth:%d", [[request URL] absoluteString], _authenticated);
    
        if (!_authenticated) {
            _authenticated = NO;
    
            _urlConnection = [[NSURLConnection alloc] initWithRequest:_request delegate:self];
    
            [_urlConnection start];
    
            return NO;
        }
        return YES;
    }
    
    
    #pragma mark - NURLConnection delegate
    
    - (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
    {
        NSLog(@"WebController Got auth challange via NSURLConnection");
    
        if ([challenge previousFailureCount] == 0)
        {
            _authenticated = YES;
    
            NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
    
            [challenge.sender useCredential:credential forAuthenticationChallenge:challenge];
    
        } else
        {
            [[challenge sender] cancelAuthenticationChallenge:challenge];
        }
    }
    
    - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;
    {
        NSLog(@"WebController received response via NSURLConnection");
    
        // remake a webview call now that authentication has passed ok.
        _authenticated = YES;
        [_web loadRequest:_request];
    
        // Cancel the URL connection otherwise we double up (webview + url connection, same url = no good!)
        [_urlConnection cancel];
    }
    
    // We use this method is to accept an untrusted site which unfortunately we need to do, as our PVM servers are self signed.
    - (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
    {
        return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
    }
    

    使用上述方法,问题未解决,继续...

    <3>方法三:

    找到appdelegate.m文件,找到@end
    在@end下面敲入如下代码。

    @implementation NSURLRequest(DataController)
    + (BOOL)allowsAnyHTTPSCertificateForHost:(NSString *)host
    {
        return YES;
    }
    @end
    

    依然未解决,继续...

    <4>方法四:

    创建AFHTTPSessionManager的类别:

    @interface NetManager : AFHTTPSessionManager
    
    + (instancetype)share;
    
    @end
    
    @implementation NetManager
    
    + (instancetype)share{
        static NetManager *manager;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
            manager = [[self alloc] initWithBaseURL:[NSURL URLWithString:kApiBaseUrl] sessionConfiguration:configuration];
            [manager setSecurityPolicy:[self customSecurityPolicy]];
        });
        
        return manager;
    }
    
    + (AFSecurityPolicy *)customSecurityPolicy {
        
        // 先导入证书 证书由服务端生成,具体由服务端人员操作
        NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"m2" ofType:@"cer"];//证书的路径 xx.cer
        NSData *cerData = [NSData dataWithContentsOfFile:cerPath];
        
        // AFSSLPinningModeCertificate 使用证书验证模式
        AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
        // allowInvalidCertificates 是否允许无效证书(也就是自建的证书),默认为NO
        // 如果是需要验证自建证书,需要设置为YES
        securityPolicy.allowInvalidCertificates = YES;
        
        //validatesDomainName 是否需要验证域名,默认为YES;
        //假如证书的域名与你请求的域名不一致,需把该项设置为NO;如设成NO的话,即服务器使用其他可信任机构颁发的证书,也可以建立连接,这个非常危险,建议打开。
        //置为NO,主要用于这种情况:客户端请求的是子域名,而证书上的是另外一个域名。因为SSL证书上的域名是独立的,假如证书上注册的域名是www.google.com,那么mail.google.com是无法验证通过的;当然,有钱可以注册通配符的域名*.google.com,但这个还是比较贵的。
        //如置为NO,建议自己添加对应域名的校验逻辑。
        securityPolicy.validatesDomainName = NO;
        
        securityPolicy.pinnedCertificates = [[NSSet alloc] initWithObjects:cerData, nil];
        return securityPolicy;
    }
    @end
    

    依然,无效...

    解决问题

    1.经过多种方法尝试,依然未解决问题,只好在QQ群中求助。
    大神果然是大神,一语中的:


    求助结果

    2.在网上看到一篇文章,发给后端看:


    WechatIMG576.png

    于是后端改了请求头,但...依然不行

    3.通过在Safari浏览器中调试后,终于发现了问题所在:
    (如何在浏览器中向h5页面传参,下篇见)


    错误信息 问题所在

    4.把报错信息及发给后端看,发现是h5页面中的接口,是http而非https,于是后端做了修改,接口改为https,又出现如下错误:

    服务器证书问题

    5.接口改为https后,依然不行,在浏览器中打开接口链接,提示证书无效:


    证书错误

    经后端的一顿操作后,证书可以了,但依然报请求头的问题,效果如下:


    WechatIMG9.jpeg

    6.还记得在刚开始的时候,后端修改请求头的事情吗?
    没错,就是那个导致了现在的局面,待后端把请求头改回去后,完美解决:


    ok

    总结

    忙活了一周,结果发现是后端的问题,好在最终完美解决了~

    相关文章

      网友评论

        本文标题:iOS - webView加载https

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