前言
年前的时候,关于苹果要强制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