iOS - webView加载https

前言

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

总结

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容