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

自苹果宣布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 里的证书博主删除了,请见谅,请大家放入自己的证书。

老的AF访问httpsDemo

新的AF访问httpsDemo

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

(0)

相关推荐

  • iOS之Https自签名证书认证及数据请求的封装原理

    摘要: 在WWDC 2016开发者大会上,苹果宣布了一个最后期限:到2017年1月1日 App Store中的所有应用都必须启用 App Transport Security安全功能.App Transport Security(ATS)是苹果在iOS 9中引入的一项隐私保护功能,屏蔽明文HTTP资源加载,连接必须经过更安全的HTTPS.苹果目前允许开发者暂时关闭ATS,可以继续使用HTTP连接,但到年底所有官方商店的应用都必须强制性使用ATS. 项目中使用的框架是AFNetworking 3.

  • IOS开发 支持https请求以及ssl证书配置详解

    IOS开发 支持https请求以及ssl证书配置详解 前言: 众所周知,苹果有言,从2017年开始,将屏蔽http的资源,强推https 楼主正好近日将http转为https,给还没动手的朋友分享一二 一.证书准备 1.证书转换 在服务器人员,给你发送的crt证书后,进到证书路径,执行下面语句 // openssl x509 -in 你的证书.crt -out 你的证书.cer -outform der 这样你就可以得到cer类型的证书了.双击,导入电脑. 2.证书放入工程 1.可以直接把转换好

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

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

  • iOS适配https证书问题(AFNetworking3.0为例)

    众所周知,苹果有言,从2017年开始,将屏蔽http的资源,强推https 楼主正好近日将http转为https,给还没动手的朋友分享一二 1.准备证书 首先找后台要一个证书(SSL证书,一般你跟后台说要弄https,然后让他给你个证书,他就知道了),我们需要的是.cer的证书.但是后台可能给我们的是.crt的证书.我们需要转换一下:打开终端 -> cd到.crt证书路径 -> 输入openssl x509 -in 你的证书.crt -out 你的证书.cer -outform der,证书就

  • iOS9苹果将原http协议改成了https协议的方法

    解决方法: 在info.plist 加入key <key>NSAppTransportSecurity</key> <dict> <key>NSAllowsArbitraryLoads</key> <true/> </dict> 下面给大家介绍ios中http 和https 协议的访问 最近做个项目,开始采用的是HTTP协议实现客户端和服务器端的交互,后来需要改成HTTPS协议.在修改的过程中发现了一些问题,解决方案如下:

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

    自苹果宣布2017年1月1日开始强制使用https以来,htpps慢慢成为大家讨论的对象之一,不是说此前https没有出现,只是这一决策让得开发者始料未及,博主在15年的时候就做过https的接口,深知此坑之深,原因就是自身对这方面知识不了解加上网上的资料少,除此外还有博客不知对错就互相转载,导致当时网上几乎找不到能用的代码,这一点,博主说的毫不夸张. 鉴于此,博主一直想填一下这个坑,多增加一些正确的代码,来供广大开发者使用,后来一直被搁置,经过尝试后,博主现将整理好的代码发布在这里,希望能帮到

  • 详解IOS开发中生成推送的pem文件

    详解IOS开发中生成推送的pem文件 具体步骤如下: 首先,需要一个pem的证书,该证书需要与开发时签名用的一致. 具体生成pem证书方法如下: 1. 登录到 iPhone Developer Connection Portal(http://developer.apple.com/iphone/manage/overview/index.action )并点击 App IDs 2. 创建一个不使用通配符的 App ID .通配符 ID 不能用于推送通知服务.例如,  com.itotem.ip

  • 详解IOS开发之实现App消息推送(最新)

    好久没有写过博客啦,今天就由本菜鸟给大家做一个简单的IOSApp消息推送教程吧!一切从0开始,包括XCode6, IOS8, 以及苹果开发者中心最新如何注册应用,申请证书以及下载配置概要文件,相信很多刚开始接触iOS的人会很想了解一下.(ps:网上看了一下虽然有很多讲述推送的好教程,我也是看着一步步学会的,但是这些教程的时间都是去年或者更早时期的,对引导新手来说不是很合适) 第一部分 首先第一步当然是介绍一下苹果的推送机制(APNS)咯(ps:其实每一篇教程都有),先来看一张苹果官方对其推送做出

  • 详解iOS开发中使用storyboard创建导航控制器的方法

    关于StoryBoard iOS5之后Apple提供了一种全新的方式来制作UI,那就是StoryBoard.简单理解来说,可以把StoryBoard看做是一组viewController对应的xib,以及它们之间的转换方式的集合.在StoryBoard中不仅可以看到每个ViewController的布局样式,也可以明确地知道各个ViewController之间的转换关系.相对于单个的xib,其代码需求更少,也由于集合了各个xib,使得对于界面的理解和修改的速度也得到了更大提升.减少代码量就是减少

  • 详解iOS开发中Keychain的相关使用

    一.Keychain 基础 根据苹果的介绍,iOS设备中的Keychain是一个安全的存储容器,可以用来为不同应用保存敏感信息比如用户名,密码,网络密码,认证令牌.苹果自己用keychain来保存Wi-Fi网络密码,VPN凭证等等.它是一个sqlite数据库,位于/private/var/Keychains/keychain-2.db,其保存的所有数据都是加密过的. 开发者通常会希望能够利用操作系统提供的功能来保存凭证(credentials)而不是把它们(凭证)保存到NSUserDefault

  • 详解iOS开发中的转场动画和组动画以及UIView封装动画

    一.转场动画 CAAnimation的子类,用于做转场动画,能够为层提供移出屏幕和移入屏幕的动画效果.iOS比Mac OS X的转场动画效果少一点 UINavigationController就是通过CATransition实现了将控制器的视图推入屏幕的动画效果 属性解析: type:动画过渡类型 subtype:动画过渡方向 startProgress:动画起点(在整体动画的百分比) endProgress:动画终点(在整体动画的百分比) 转场动画代码示例 1.界面搭建 2.实现代码 复制代码

  • 详解iOS开发中UItableview控件的数据刷新功能的实现

    实现UItableview控件数据刷新 一.项目文件结构和plist文件 二.实现效果 1.说明:这是一个英雄展示界面,点击选中行,可以修改改行英雄的名称(完成数据刷新的操作). 运行界面: 点击选中行: 修改数据后自动刷新: 三.代码示例 数据模型部分: YYheros.h文件 复制代码 代码如下: // //  YYheros.h //  10-英雄展示(数据刷新) // //  Created by apple on 14-5-29. //  Copyright (c) 2014年 itc

  • 详解iOS开发中UITableview cell 顶部空白的多种设置方法

    我知道没人会主动设置这个东西,但是大家一定都遇到过这个问题,下面总结下可能是哪些情况: 1, self.automaticallyAdjustsScrollViewInsets = NO; 这个应该是最常见而且不容易被发现的原因,起因是iOS7在Conttoller中新增了automaticallyAdjustsScrollViewInsets这个属性,当设置为YES时(默认YES),如果视图里面存在唯一一个UIScrollView或其子类View,那么它会自动设置相应的内边距,这样可以让scr

  • 详解IOS开发中图片上传时两种图片压缩方式的比较

    IOS 图片上传时两种图片压缩方式的比较 上传图片不全面的想法:把图片保存到本地,然后把图片的路径上传到服务器,最后又由服务器把路径返回,这种方式不具有扩展性,如果用户换了手机,那么新手机的沙盒中就没有服务器返回的图片路径了,此时就无法获取之前已经上传了的头像了,在项目中明显的不可行. 上传图片的正确方式:上传头像到服务器一般是将图片NSData上传到服务器,服务器返回一个图片NSString地址,之后再将NSString的路径转为url并通过url请求去更新用户头像(用户头像此时更新的便是NS

  • 详解iOS开发中app的归档以及偏好设置的存储方式

    ios应用数据存储方式(归档) 一.简单说明 在使用plist进行数据存储和读取,只适用于系统自带的一些常用类型才能用,且必须先获取路径相对麻烦: 偏好设置(将所有的东西都保存在同一个文件夹下面,且主要用于存储应用的设置信息) 归档:因为前两者都有一个致命的缺陷,只能存储常用的类型.归档可以实现把自定义的对象存放在文件中. 二.代码示例 1.文件结构 2.代码示例 YYViewController.m文件 复制代码 代码如下: // //  YYViewController.m //  02-归

随机推荐