iOS实用篇:Https双向认证

年前的时候,关于苹果要强制https的传言四起,虽然结果只是一个“谣言”,但是很明显的这是迟早会到来的,间接上加速了各公司加紧上https的节奏,对于iOS客户端来说,上https需不需要改变一些东西取决于---------对,就是公司有没有钱。土豪公司直接买买买,iOS开发者只需要把http改成https完事。然而很不幸,我们在没钱的公司,选择了自签证书。虽然网上很多关于https的适配,然而很多都是已过时的,这里我们主要是讲一下https双向认证。

【证书选择】自签
【网络请求】原生NSURLSession或者AFNetworking3.0以上版本
【认证方式】双向认证

Https双向认证过程

先来了解一下双向认证的大体过程:(图片来自网络,如果是某位博主原创的请私信我)

httpsLine.png

下面我们一步步来实现

1、设置服务端证书

    NSString *certFilePath = [[NSBundle mainBundle] pathForResource:@"server" ofType:@"cer"];
    NSData *certData = [NSData dataWithContentsOfFile:certFilePath];
    NSSet *certSet = [NSSet setWithObject:certData];
    AFSecurityPolicy *policy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate withPinnedCertificates:certSet];
    policy.allowInvalidCertificates = YES;
    policy.validatesDomainName = NO;
    self.afnetworkingManager.securityPolicy = policy;

2、处理挑战

原生的NSURLSession是在

- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(nonnull NSURLAuthenticationChallenge *)challenge completionHandler:(nonnull void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler 

代理方法里面处理挑战的,再看看AFNetworking在该代理方法里处理的代码

    if (self.taskDidReceiveAuthenticationChallenge) {
        disposition = self.taskDidReceiveAuthenticationChallenge(session, task, challenge, &credential);
    } else {
        ...
    }

我们只需要给它传递一个处理的block

[self.afnetworkingManager setSessionDidReceiveAuthenticationChallengeBlock:^NSURLSessionAuthChallengeDisposition(NSURLSession*session, NSURLAuthenticationChallenge *challenge, NSURLCredential *__autoreleasing*_credential) {
        ...
}

根据传来的challenge生成disposition(应对挑战的方式)和credential(客户端生成的挑战证书)

3、服务端认证

当challenge的认证方法为NSURLAuthenticationMethodServerTrust时,需要客户端认证服务端证书

//评估服务端安全性
if([weakSelf.afnetworkingManager.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
                //创建凭据
                credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
                if(credential) {
                    disposition =NSURLSessionAuthChallengeUseCredential;
                } else {
                    disposition =NSURLSessionAuthChallengePerformDefaultHandling;
                }
            } else {
                disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
            }

4、客户端认证

认证完服务端后,需要认证客户端
由于是双向认证,这一步是必不可省的

            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:not exist");
            }
            else
            {
                NSData *PKCS12Data = [NSData dataWithContentsOfFile:p12];
                
                if ([[weakSelf 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];
                    disposition =NSURLSessionAuthChallengeUseCredential;
                }
            }
+ (BOOL)extractIdentity:(SecIdentityRef*)outIdentity andTrust:(SecTrustRef *)outTrust fromPKCS12Data:(NSData *)inPKCS12Data {
    OSStatus securityError = errSecSuccess;
    //client certificate password
    NSDictionary*optionsDictionary = [NSDictionary dictionaryWithObject:@"your p12 file pwd"
                                                                 forKey:(__bridge id)kSecImportExportPassphrase];
    
    CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
    securityError = SecPKCS12Import((__bridge CFDataRef)inPKCS12Data,(__bridge CFDictionaryRef)optionsDictionary,&items);
    
    if(securityError == 0) {
        CFDictionaryRef myIdentityAndTrust =CFArrayGetValueAtIndex(items,0);
        const void*tempIdentity =NULL;
        tempIdentity= CFDictionaryGetValue (myIdentityAndTrust,kSecImportItemIdentity);
        *outIdentity = (SecIdentityRef)tempIdentity;
        const void*tempTrust =NULL;
        tempTrust = CFDictionaryGetValue(myIdentityAndTrust,kSecImportItemTrust);
        *outTrust = (SecTrustRef)tempTrust;
    } else {
        NSLog(@"Failedwith error code %d",(int)securityError);
        return NO;
    }
    return YES;
}

原生NSURLSession双向认证

在原生的代理方法里面认证就行,代码基本和AFNetworking的一致,注意最后需要调用

 completionHandler(NSURLSessionAuthChallengeUseCredential, credential);

来执行回调操作

关于UIWebView的Https双向认证

网上的资料大体上有几种解决方法
1:跳过Https认证(这还能跳过?没试过,不太靠谱)
2:中断原有的请求步骤,将request拿出来,下载完整的HTML代码,让webView加载该代码(在单页面展示的情况下基本满足使用,但是在部分标签不是独立跳转https路径的时候,将出现无法加载的情况,不是很好用)

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
    NSString * urlString = [request.URL absoluteString];
    if ([urlString containsString:URL_API_BASE]) {
        [[SUHTTPOperationManager manager]REQUEST:request progress:nil handler:^(BOOL isSucc, id responseObject, NSError *error) {
            NSString * htmlString = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];
            BASE_INFO_FUN(@"下载HTML完毕");
            [self loadHTMLString:htmlString baseURL:nil];
        }];
        return NO;
    }
    return YES;
}

3、中断原有的请求步骤,将request拿出来,完成鉴权认证之后,再让webView重新请求该request(这种方式理论上好像可以,我试过,没有成功,可能我打开的方式不正确)
4、或许,您有更好的解决方案 - -

关于代码

网上很多https双向认证的代码,基本是一样的,这里我们直接拿来用就可以,前提是我们不能单纯copy,而是在理解其实现的基础上,整合到工程中,遇到问题解决思路清晰,而不是一脸懵逼。

PS:大半年没更新文章了,也没怎么上简书,发现还是有很多人支持的,有朋友的私信也没回复,表示歉意。

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

推荐阅读更多精彩内容

  • 本想在这篇文章中单独写AFNetworking 3.0中AFSecurityPolicy的源码阅读笔记的。但随着源...
    WeiHing阅读 2,500评论 1 13
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • 写在前面: 1.相关https具体内容本篇就不再描述。 2.前几天公司项目要求配置https双向认证,由于是银行业...
    葛高召阅读 1,851评论 1 5
  • 还是习惯去你的朋友圈,去寻找你的足迹。只是我不再留言,不再留下痕迹,不想让彼此更累。心还是痛的,默默为我们的无缘而...
    D038_礼恩_佛山阅读 141评论 0 3
  • 夏日酷暑,头顶着烈日灼心,不曾有褪去热浪的意思。就连傍晚的风,也如刚从锅炉里钻出来的一样。摸着发烫的台阶,我硬着头...
    冰雪倾心阅读 209评论 4 4