当前位置: 移动技术网 > IT编程>移动开发>IOS > 详解iOS开发 - 用AFNetworking实现https单向验证,双向验证

详解iOS开发 - 用AFNetworking实现https单向验证,双向验证

2019年07月24日  | 移动技术网IT编程  | 我要评论

我外母唔系人粤语,seseyou,河南四维彩超预约ok

自苹果宣布2017年1月1日开始强制使用https以来,htpps慢慢成为大家讨论的对象之一,不是说此前https没有出现,只是这一决策让得开发者始料未及,博主在15年的时候就做过https的接口,深知此坑之深,原因就是自身对这方面知识不了解加上网上的资料少,除此外还有博客不知对错就互相转载,导致当时网上几乎找不到能用的代码,这一点,博主说的毫不夸张。

鉴于此,博主一直想填一下这个坑,多增加一些正确的代码,来供广大开发者使用,后来一直被搁置,经过尝试后,博主现将整理好的代码发布在这里,希望能帮到焦急寻找的开发者。

1.先来说说老的afnetworking2.x怎么来实现的

博主在网上看过几篇帖子,其中说的一些方法是正确的,但是却并不全对,由于那几篇博客几乎一样,博主不能确定最早的那篇是谁写的,所以就重新在下面说明下方法:

1)倒入client.p12证书;

2)在plist文件做如图配置:

3)在afnetworking中修改一个类:

找到这个文件,在里面增加一个方法:

- (osstatus)extractidentity:(cfdataref)inp12data toidentity:(secidentityref*)identity { 
  osstatus securityerror = errsecsuccess;
  cfstringref password = cfstr("证书密码"); 
  const void *keys[] = { ksecimportexportpassphrase };
  const void *values[] = { password };
  cfdictionaryref options = cfdictionarycreate(null, keys, values, 1, null, null);
  cfarrayref items = cfarraycreate(null, 0, 0, null); 
  securityerror = secpkcs12import(inp12data, options, &items);
  if (securityerror == 0)   
  {
    cfdictionaryref ident = cfarraygetvalueatindex(items,0); 
    const void *tempidentity = null; 
    tempidentity = cfdictionarygetvalue(ident, ksecimportitemidentity);
    *identity = (secidentityref)tempidentity;  
  } 
  if (options) {  
    cfrelease(options);  
  }
  return securityerror;
}

再修改一个方法:

用下面的这段代码替换nsurlconnectiondelegate中的同名代码,

- (void)connection:(nsurlconnection *)connection
willsendrequestforauthenticationchallenge:(nsurlauthenticationchallenge *)challenge
{
  nsstring *thepath = [[nsbundle mainbundle] pathforresource:@"client" oftype:@"p12"];
  //倒入证书    nslog(@"thepath===========%@",thepath);
  nsdata *pkcs12data = [[nsdata alloc] initwithcontentsoffile:thepath];
  cfdataref inpkcs12data = (__bridge cfdataref)pkcs12data;

  secidentityref identity = null;
  // extract the ideneity from the certificate
  [self extractidentity :inpkcs12data toidentity:&identity];

  seccertificateref certificate = null;
  secidentitycopycertificate (identity, &certificate);

  const void *certs[] = {certificate};
  //            cfarrayref certarray = cfarraycreate(kcfallocatordefault, certs, 1, null);
  // create a credential from the certificate and ideneity, then reply to the challenge with the credential
  //nslog(@"identity=========%@",identity);
  nsurlcredential *credential = [nsurlcredential credentialwithidentity:identity certificates:nil persistence:nsurlcredentialpersistencepermanent];

  //      credential = [nsurlcredential credentialwithidentity:identity certificates:(__bridge nsarray*)certarray persistence:nsurlcredentialpersistencepermanent];

  [challenge.sender usecredential:credential forauthenticationchallenge:challenge];

}

4)发起请求

 nsstring *url = @"xxxxxxxxxx";
  // 1.获得请求管理者
  afhttprequestoperationmanager *mgr = [afhttprequestoperationmanager manager];
  //2设置https 请求
  afsecuritypolicy *securitypolicy = [afsecuritypolicy policywithpinningmode:afsslpinningmodecertificate];
  securitypolicy.allowinvalidcertificates = yes;
  mgr.securitypolicy = securitypolicy;
  // 3.发送post请求

  [mgr post:url parameters:nil success:^(afhttprequestoperation * _nonnull operation, id _nonnull responseobject) {
    nslog(@"responseobject: %@", responseobject);

  } failure:^(afhttprequestoperation * _nonnull operation, nserror * _nonnull error) {
    nslog(@"error: %@", error);

  }];

到此,老版的afnetworking请求https接口的双向验证就做完了,但是有一个问题,这里需要改动afnetworking的代码,何况新的afnetworking已经有了,为了保持代码的活力,老的应该摒弃的,而且更新pods后肯定替换的代码就没了,也是一个问题,不要急,下面来说说怎么用新的afnetworking,并解决被pods更新替换代码的问题。

最后再说一点,使用老的af来请求,只用到了client.p12文件,并没有用到server.cer,在新的里面是有用到的,猜测可能是客户端选择信任任何证书导致的,就变成了单向的验证。

demo放在最后

2.来说说新的afnetworking3.x怎么来实现的

1)倒入client.p12和server.cer文件

2)plist内的设置,这是和上面一样的:

3)这里可不需要修改类里面的代码,但是这里需要重写一个方法:

 nsstring *url = @"https://test.niuniuhaoguanjia.com/3.0.0/?service=city.getcitylist";

  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;

  _manager = [afhttpsessionmanager manager];
  _manager.securitypolicy = policy;
  _manager.requestserializer = [afhttprequestserializer serializer];
  _manager.responseserializer = [afhttpresponseserializer serializer];
  _manager.responseserializer.acceptablecontenttypes = [nsset setwithobjects:@"application/json", @"text/json", @"text/javascript",@"text/plain", nil];
  //关闭缓存避免干扰测试r
  _manager.requestserializer.cachepolicy = nsurlrequestreloadignoringlocalcachedata;
  [_manager setsessiondidbecomeinvalidblock:^(nsurlsession * _nonnull session, nserror * _nonnull error) {
    nslog(@"setsessiondidbecomeinvalidblock");
  }];
  //客户端请求验证 重写 setsessiondidreceiveauthenticationchallengeblock 方法
  __weak typeof(self)weakself = self;
  [_manager setsessiondidreceiveauthenticationchallengeblock:^nsurlsessionauthchallengedisposition(nsurlsession*session, nsurlauthenticationchallenge *challenge, nsurlcredential *__autoreleasing*_credential) {
    nsurlsessionauthchallengedisposition disposition = nsurlsessionauthchallengeperformdefaulthandling;
    __autoreleasing nsurlcredential *credential =nil;
    if([challenge.protectionspace.authenticationmethod isequaltostring:nsurlauthenticationmethodservertrust]) {
      if([weakself.manager.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;
      }
    } else {
      // client authentication
      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;
        }
      }
    }
    *_credential = credential;
    return disposition;
  }];

4)发起请求

//第三步和这一步代码是放在一起的,请注意哦
  [_manager get:url parameters:nil progress:^(nsprogress * _nonnull downloadprogress) {

  } success:^(nsurlsessiondatatask * _nonnull task, id _nullable responseobject) {
    nsdictionary *dic = [nsjsonserialization jsonobjectwithdata:responseobject options:nsjsonreadingmutablecontainers error:nil];
    nslog(@"json: %@", dic);
  } failure:^(nsurlsessiondatatask * _nullable task, nserror * _nonnull error) {
    nslog(@"error: %@", error);

    nsdata *data = [error.userinfo objectforkey:@"com.alamofire.serialization.response.error.data"];
    nsstring *str = [[nsstring alloc]initwithdata:data encoding:nsutf8stringencoding];
    nslog(@"%@",str);
  }];

另外还要加上一个方法:

+(bool)extractidentity:(secidentityref*)outidentity andtrust:(sectrustref *)outtrust frompkcs12data:(nsdata *)inpkcs12data {
  osstatus securityerror = errsecsuccess;
  //client certificate password
  nsdictionary*optionsdictionary = [nsdictionary dictionarywithobject:@"证书密码"
                                 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;
}

没错,我们是要封装一下,可是要怎么封装呢?博主尝试了集中都失败了,真是百思不得解,相信主动去封装的开发者也会碰到封装后请求失败的问题,也许你成功了,但是这里需要注意一个在block内使用变量的问题,具体的可以去看博主怎么封装的。

到这里,新的af请求https就已经结束了,想看封装的,demo放在最后。

3.单向验证

说到这个,不得不说一下网上的很多方法,都把单向验证当作双向的,其实也是并不理解其原理,关于原理,请看这里
代码实现af都是一样的:

//af加上这句和下面的方法
  _manager.securitypolicy = [self customsecuritypolicy];



/**** ssl pinning ****/
- (afsecuritypolicy*)customsecuritypolicy {
  nsstring *cerpath = [[nsbundle mainbundle] pathforresource:@"server" oftype:@"cer"];
  nsdata *certdata = [nsdata datawithcontentsoffile:cerpath];
  afsecuritypolicy *securitypolicy = [afsecuritypolicy policywithpinningmode:afsslpinningmodecertificate];
  [securitypolicy setallowinvalidcertificates:yes];
  nsset *set = [nsset setwithobjects:certdata, nil];
  [securitypolicy setpinnedcertificates:@[certdata]];
  /**** ssl pinning ****/
  return securitypolicy;
}

4.demo下载福利

因为证书安全问题,demo 里的证书博主删除了,请见谅,请大家放入自己的证书。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持移动技术网。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

  • ios uicollectionview实现横向滚动

    现在使用卡片效果的app很多,之前公司让实现一种卡片效果,就写了一篇关于实现卡片的文章。文章最后附有demo实现上我选择了使用uicollectionview ... [阅读全文]
  • iOS UICollectionView实现横向滑动

    本文实例为大家分享了ios uicollectionview实现横向滑动的具体代码,供大家参考,具体内容如下uicollectionview的横向滚动,目前我使... [阅读全文]
  • iOS13适配深色模式(Dark Mode)的实现

    iOS13适配深色模式(Dark Mode)的实现

    好像大概也许是一年前, mac os系统发布了深色模式外观, 看着挺刺激, 时至今日用着也还挺爽的终于, 随着iphone11等新手机的发售, ios 13系统... [阅读全文]
  • ios 使用xcode11 新建项目工程的步骤详解

    ios 使用xcode11 新建项目工程的步骤详解

    xcode11新建项目工程,新增了scenedelegate这个类,转而将原appdelegate负责的对ui生命周期的处理担子接了过来。故此可以理解为:ios... [阅读全文]
  • iOS实现转盘效果

    本文实例为大家分享了ios实现转盘效果的具体代码,供大家参考,具体内容如下demo下载地址: ios转盘效果功能:实现了常用的ios转盘效果,轮盘抽奖效果的实现... [阅读全文]
  • iOS开发实现转盘功能

    本文实例为大家分享了ios实现转盘功能的具体代码,供大家参考,具体内容如下今天给同学们讲解一下一个转盘选号的功能,直接上代码直接看viewcontroller#... [阅读全文]
  • iOS实现轮盘动态效果

    本文实例为大家分享了ios实现轮盘动态效果的具体代码,供大家参考,具体内容如下一个常用的绘图,主要用来打分之类的动画,效果如下。主要是ios的绘图和动画,本来想... [阅读全文]
  • iOS实现九宫格连线手势解锁

    本文实例为大家分享了ios实现九宫格连线手势解锁的具体代码,供大家参考,具体内容如下demo下载地址:效果图:核心代码://// clockview.m// 手... [阅读全文]
  • iOS实现卡片堆叠效果

    本文实例为大家分享了ios实现卡片堆叠效果的具体代码,供大家参考,具体内容如下如图,这就是最终效果。去年安卓5.0发布的时候,当我看到安卓全新的material... [阅读全文]
  • iOS利用余弦函数实现卡片浏览工具

    iOS利用余弦函数实现卡片浏览工具

    本文实例为大家分享了ios利用余弦函数实现卡片浏览工具的具体代码,供大家参考,具体内容如下一、实现效果通过拖拽屏幕实现卡片移动,左右两侧的卡片随着拖动变小,中间... [阅读全文]
验证码:
移动技术网