前言
年前的时候,关于苹果要强制https的传言四起,虽然结果只是一个“谣言”,但是很明显的这是迟早会到来的,间接上加速了各公司加紧上https的节奏,对于ios客户端来说,上https需不需要改变一些东西取决于---------对,就是公司有没有钱。土豪公司直接买买买,ios开发者只需要把http改成https完事。然而很不幸,我们在没钱的公司,选择了自签证书。虽然网上很多关于https的适配,然而很多都是已过时的,这里我们主要是讲一下https双向认证。
【证书选择】自签
【网络请求】原生nsurlsession或者afnetworking3.0以上版本
【认证方式】双向认证
https双向认证过程
先来了解一下双向认证的大体过程:(图片来自网络,如果是某位博主原创的请私信我)
下面我们一步步来实现
1、设置服务端证书
1
2
3
4
5
6
7
|
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是在
1
|
- ( void )urlsession:(nsurlsession *)session didreceivechallenge:(nonnull nsurlauthenticationchallenge *)challenge completionhandler:(nonnull void (^)(nsurlsessionauthchallengedisposition, nsurlcredential * _nullable))completionhandler |
代理方法里面处理挑战的,再看看afnetworking在该代理方法里处理的代码
1
2
3
4
5
|
if (self.taskdidreceiveauthenticationchallenge) { disposition = self.taskdidreceiveauthenticationchallenge(session, task, challenge, &credential); } else { ... } |
我们只需要给它传递一个处理的block
1
2
3
|
[self.afnetworkingmanager setsessiondidreceiveauthenticationchallengeblock:^nsurlsessionauthchallengedisposition(nsurlsession*session, nsurlauthenticationchallenge *challenge, nsurlcredential *__autoreleasing*_credential) { ... } |
根据传来的challenge生成disposition(应对挑战的方式)和credential(客户端生成的挑战证书)
3、服务端认证
当challenge的认证方法为nsurlauthenticationmethodservertrust时,需要客户端认证服务端证书
1
2
3
4
5
6
7
8
9
10
11
12
|
//评估服务端安全性 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、客户端认证
认证完服务端后,需要认证客户端
由于是双向认证,这一步是必不可省的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
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; } } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
+ ( 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的一致,注意最后需要调用
1
|
completionhandler(nsurlsessionauthchallengeusecredential, credential); |
来执行回调操作
关于uiwebview的https双向认证
网上的资料大体上有几种解决方法
1:跳过https认证(这还能跳过?没试过,不太靠谱)
2:中断原有的请求步骤,将request拿出来,下载完整的html代码,让webview加载该代码(在单页面展示的情况下基本满足使用,但是在部分标签不是独立跳转https路径的时候,将出现无法加载的情况,不是很好用)
1
2
3
4
5
6
7
8
9
10
11
12
|
- ( 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,而是在理解其实现的基础上,整合到工程中,遇到问题解决思路清晰,而不是一脸懵逼。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对服务器之家的支持。
原文链接:http://www.jianshu.com/p/72bf60b5f94d