iOS实用教程之Https双向认证详解

时间:2021-07-16 01:10:54

前言

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

【证书选择】自签

【网络请求】原生nsurlsession或者afnetworking3.0以上版本

【认证方式】双向认证

https双向认证过程

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

iOS实用教程之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