iOS开发 AFNetworking 3.0 HTTPS的使用

为了迎合iOS新版本的升级, AFNetworking在3.0版本中删除了基于 NSURLConnection API的所有支持。如果你的项目以前使用过这些API,建议您立即升级到基于 NSURLSession 的API的AFNetworking的版本。AFNetworking 3.0正式支持的iOS 7, Mac OS X的10.9, watchOS 2 , tvOS 9 和Xcode 7。本文将从以下几点说明:

首先说明AFNetworking的集成过程

1、从github下载AFNetworking3.0,把AFNetworking.h 和 UIKit+AFNetworking.h导入工程

(下载地址:https://github.com/AFNetworking/AFNetworking.git)

注意!!在AFNetworking中是不支持text/html的

遇到请求头到错误时在AFURLResponseSerialization.m里修改

1 //把@"text/html" 添加到下面代码里就行,
2 self.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript",@"text/html",@"text/plain", nil]; 

或者在需要的地方写代码

1 //设置请求头contenttypes信息
2      manager.responseSerializer.acceptableContentTypes = [manager.responseSerializer.acceptableContentTypes setByAddingObject:@"text/html"];

2、简单的使用封装

AFNetworking的使用主要分为三步: 
(1) 创建会话(Session) 
(2) 创建请求(request) 
(3) 创建任务(task),对数据进行上传。

//初始化
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];

 如果后台接口是二进制格式

//默认的
manager.requestSerializer = [AFHTTPRequestSerializer serializer];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];

如果后台接口是JSON格式

manager.requestSerializer = [AFJSONRequestSerializer serializer];
manager.responseSerializer = [AFJSONResponseSerializer serializer];
GET Request
[manager GET:@"example" parameters:parameters progress:^(NSProgress * _Nonnull downloadProgress) {
        //这里可以用来显示下载进度
    } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        //成功
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        //失败
    }];
POST  Request
//post请求参数一般用字典,键要与请求url的参数名一致。
[manager POST:@"example" parameters:parameters progress:^(NSProgress * _Nonnull uploadProgress) {
        //进度
    } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        //成功
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        //失败
    }];

Shared Network Reachability(网络状态的监控)

//监听网络
   /*
    AFNetworkReachabilityStatusUnknown          = -1,
    AFNetworkReachabilityStatusNotReachable     = 0,
    AFNetworkReachabilityStatusReachableViaWWAN = 1,
    AFNetworkReachabilityStatusReachableViaWiFi = 2,
    NSString * AFStringFromNetworkReachabilityStatus(AFNetworkReachabilityStatus status) {
    switch (status) {
        case AFNetworkReachabilityStatusNotReachable:
            return NSLocalizedStringFromTable(@"Not Reachable", @"AFNetworking", nil);
        case AFNetworkReachabilityStatusReachableViaWWAN:
            return NSLocalizedStringFromTable(@"Reachable via WWAN", @"AFNetworking", nil);
        case AFNetworkReachabilityStatusReachableViaWiFi:
            return NSLocalizedStringFromTable(@"Reachable via WiFi", @"AFNetworking", nil);
        case AFNetworkReachabilityStatusUnknown:
        default:
            return NSLocalizedStringFromTable(@"Unknown", @"AFNetworking", nil);
    }
}
   */    
    [[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {     
     NSLog(@"Reachability: %@", AFStringFromNetworkReachabilityStatus(status))  
 }];
[[AFNetworkReachabilityManager sharedManager] startMonitoring];
//单纯判断有无网络可以用isReachable属性
if ([[AFNetworkReachabilityManager manager] isReachable] == NO) {
    //do something  
}
上传图片

图片上传的时候要问后台只能单张上传或者多张上传.

//单张上传
        [manager POST:url parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData>  _Nonnull formData) {
            //创建待上传图片的名称,为了不重名,一般使用当前时间+图片类型(eg .png)为名称
            NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
            formatter.dateFormat = @"yyyyMMddHHmmss";
            //服务器保存上传图片的路径,这个路径后台给的话用后台的,没给就自己创建(此处没给)
            NSString *name = [formatter stringFromDate:[NSDate date]];
            //后缀  .png   .jpg
            NSString *fileName = [[formatter stringFromDate:[NSDate date]] stringByAppendingString:@".jpg"];
            /*
             *UIImageJPEGRepresentation(image,1.0)   //待压缩比例
             *UIImagePNGRepresentation(image)
             *这是iOS自带的将UIImage对象转成NSData
             */
            NSData *data = UIImageJPEGRepresentation(image,1.0);
            //开始上传
            [formData appendPartWithFileData:data name:name fileName:fileName mimeType:@"image/jpg"];
            
        } progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
            //根据responseObject获取后台返回的内容
            
        } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        }];
/*
  * 多张上传和单张上传的区别:
  * 图片名要按时间+数组中图片的index+.jpg
  * 服务器保存图片的路径按时间+数组中图片的index
  */
  //多张上传
    //图片上传
    for (int i = 0; i < imageArr.count; i ++) {
       [manager POST:@"http://112.54.80.235:50406/IndustryPioneer.svc/uploadimageIos" parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData>  _Nonnull formData) {
            //创建待上传图片的名称,为了不重名,一般使用当前时间+图片类型(eg .png)为名称
            NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
            formatter.dateFormat = @"yyyyMMddHHmmss";
            //服务器保存上传图片的路径,这个路径后台给的话用后台的,没给就自己创建(此处没给)
            NSString *name = [NSString stringWithFormat:@"%@%d",[formatter stringFromDate:[NSDate date]],i];
            //后缀  .png   .jpg
            NSString *fileName = [[NSString stringWithFormat:@"%@%d",[formatter stringFromDate:[NSDate date]],i] stringByAppendingString:@".jpg"];
            /*
             *UIImageJPEGRepresentation(image,1.0)   //待压缩比例
             *UIImagePNGRepresentation(image)
             *这是iOS自带的将UIImage对象转成NSData
             */
            
            NSData *data = UIImageJPEGRepresentation([imageArr objectAtIndex :i],1.0);
            //开始上传
            [formData appendPartWithFileData:data name:name fileName:fileName mimeType:@"image/jpg"];
            
        } progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
            //根据responseObject获取后台返回的内容
 
        } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {

        }];

    }

然后谈下2.x之前的版本和3.0以后的版本的区别

1、在3.0中NSURLConnection的API已废弃

AFNetworking 1.0建立在NSURLConnection的基础API之上 ,AFNetworking 2.0开始使用NSURLConnection的基础API ,以及较新基于NSURLSession的API的选项。 AFNetworking 3.0现已完全基于NSURLSession的API,这降低了维护的负担,同时支持苹果增强关于NSURLSession提供的任何额外功能。由于Xcode 7中,NSURLConnection的API已经正式被苹果弃用。虽然该API将继续运行,但将没有新功能将被添加,并且苹果已经通知所有基于网络的功能,以充分使NSURLSession向前发展。

AFNetworking 2.X将继续获得关键的隐患和安全补丁,但没有新的功能将被添加。Alamofire(Swift下的网络请求)软件基金会建议,所有的项目迁移到基于NSURLSession的API。

2、弃用的类

下面的类已从AFNetworking 3.0中废弃:

  • AFURLConnectionOperation
  • AFHTTPRequestOperation
  • AFHTTPRequestOperationManager

3、修改的类

下面的类包含基于NSURLConnection的API的内部实现。他们已经被使用NSURLSession重构:

  • UIImageView+AFNetworking
  • UIWebView+AFNetworking
  • UIButton+AFNetworking

4、2.x向3.x的过渡,迁移

AFHTTPRequestOperationManager 核心代码

如果你以前使用 AFHTTPRequestOperationManager , 你将需要迁移去使用 AFHTTPSessionManager。 以下的类在两者过渡间并没有变化:

  • securityPolicy
  • requestSerializer
  • responseSerializer

接下来举一个关于AFHTTPSessionManager的简单例子。注意HTTP网络请求返回的不再是AFHTTPRequestOperation, 修改成为了NSURLSessionTask,并且成功和失败的Block块中的参数也变更为了NSURLSessionTask,而不再是AFHTTPRequestOperation。

AFNetworking 2.x

AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:@"请求的url" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
        NSLog(@"成功");
} failure:^(AFHTTPRequestOperation *operation, NSError*error) {
        NSLog(@"失败");
}];

AFNetworking 3.0

AFHTTPSessionManager *session = [AFHTTPSessionManager manager];
[session GET:@"请求的url" parameters:nil success:^(NSURLSessionDataTask *task, id responseObject) {
        NSLog(@"成功");
} failure:^(NSURLSessionDataTask *task, NSError *error) {
        NSLog(@"失败");        
}];
AFHTTPRequestOperation 核心代码

与NSURLConnection对象不同,每个共享应用范围的设置如会话管理、缓存策略、Cookie存储以及URL协议等,这些NSURLSession对象都可以单独进行配置。使用特定的配置来初始化会话,它可以发送任务来获取数据,并上传或下载文件。

在AFNetworking 2.0中,使用AFHTTPRequestOperation,有可能创建一个没有额外开销的独立的网络请求来获取数据。NSURLSession则需要更多的开销,为了获得所要请求的数据。

接下来,将要通过AFHTTPSessionManager创建一个对象,并创建一个任务和启动它。

AFNetworking 2.x

NSURL *URL = [NSURL URLWithString:@""];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
AFHTTPRequestOperation *op = [[AFHTTPRequestOperation alloc] initWithRequest:request];
op.responseSerializer = [AFJSONResponseSerializer serializer];
[op setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
        NSLog(@"JSON: %@", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
        NSLog(@"Error: %@", error);
}];
[[NSOperationQueue mainQueue] addOperation:op];

AFNetworking 3.0

NSURL *URL = [NSURL URLWithString:@""];
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager GET:URL.absoluteString parameters:nil success:^(NSURLSessionTask *task, id responseObject) {
        NSLog(@"JSON: %@", responseObject);
} failure:^(NSURLSessionTask *operation, NSError *error) {
        NSLog(@"Error: %@", error);
}];
5、UIKit的迁移

图片下载已经被重构,以遵循AlamofireImage架构与新的AFImageDownloader类。这个类的图片下载职责的代理人是UIButton与UIImageView的类目,并且提供了一些方法,在必要时可以自定义。类别中,下载远程图片的实际方法没有改变。

UIWebView的类目被重构为使用AFHTTPSessionManager作为其网络请求。

6、UIAlertView的类目被废弃

从AFNetworking 3.0后UIAlertView的类目因过时而被废弃。并没有提供UIAlertController类目的计划,因为这是应用程序应处理的逻辑,而不是这个库。

最后说说HTTPS的问题

一般来讲如果app用了web service , 我们需要防止数据嗅探来保证数据安全.通常的做法是用ssl来连接以防止数据抓包和嗅探

      其实这么做的话还是不够的 。 我们还需要防止中间人攻击(不明白的自己去百度)。攻击者通过伪造的ssl证书使app连接到了伪装的假冒的服务器上,这是个严重的问题!那么如何防止中间人攻击呢?首先web服务器必须提供一个ssl证书,需要一个 .crt 文件,然后设置app只能连接有效ssl证书的服务器。

一、HTTPS 

HTTPS就是将HTTP协议数据包放到SSL/TSL层加密后,在TCP/IP层组成IP数据报去传输,以此保证传输数据的安全;而对于接收端,在SSL/TSL将接收的数据包解密之后,将数据传给HTTP协议层,就是普通的HTTP数据。HTTP和SSL/TSL都处于OSI模型的应用层。

二、App Transport Security

iOS9中新增App Transport Security(简称ATS)特性, 主要使到原来请求的时候用到的HTTP,都转向TLS1.2协议进行传输。这也意味着所有的HTTP协议都强制使用了HTTPS协议进行传输。

一般我们如果还是使用的http,不更新的话,可通过在 Info.plist 中声明,倒退回不安全的网络请求。

<key>NSAppTransportSecurity</key>
  <dict>
        <key>NSAllowsArbitraryLoads</key>
        <true/>
 </dict>

三、注意用HTTPS的话

These are the App Transport Security requirements:

  • The protocol Transport Security Layer (TLS) must be at least version 1.2.
  • Connection ciphers are limited to those that provide forward secrecy (see the list of ciphers below.)
  • Certificates must use at least an SHA256 fingerprint with either a 2048 bit or greater RSA key, or a 256 bit or greater Elliptic-Curve (ECC) key.

根据原文描述,首先必须要基于TLS 1.2版本协议。再来就是连接的加密方式要提供Forward Secrecy,文档中罗列出支持的加密算法(如下表)。最后就是证书至少要使用一个SHA256的指纹与任一个2048位或者更高位的RSA密钥,或者是256位或者更高位的ECC密钥。如果不符合其中一项,请求将被中断并返回nil。

  • 第一条就是TLS版本所需要支持的协议

    1240

    第一条满足

  • 第一条就是连接的加密方式需要提供“Foward Secrecy”这个东东,下面是支持Forward Secrecy的加密方式

      TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
      TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
      TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384
      TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
      `TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256`
      TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
      TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
      TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
      TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
      TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
      TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA

这是一个满足条件的证书信息中的情况

1240

满足上面中的条件

但是也要注意证书的合法性,注意是否有效,iOS要求连接的HTTPS站点必须为CA签名过的合法证书。

1240

证书不被信任

1240

证书未经过验证

1240

有效

注意以上不同的情况决定了AFSecurityPolicy--setAllowInvalidCertificates:是否要验证证书的有效性。

四、使用AFNetworking中https的连接(3.0.0之前)

AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy defaultPolicy];
//allowInvalidCertificates 是否允许无效证书(也就是自建的证书),默认为NO//如果是需要验证自建证书,需要设置为YES
securityPolicy.allowInvalidCertificates = YES;
//validatesDomainName 是否需要验证域名,默认为YES;  
securityPolicy.validatesDomainName = YES;
manager.securityPolicy  = securityPolicy;
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
[manager POST:urlString parameters:dic success:finishedBlock failure:failedBlock];

但是刚开始一直不行,不能正常访问数据

  • 第一个错是我打开 Clarles,忘记关了
  • 老出现`Error: Error Domain=NSURLErrorDomain Code=-1012

第二种情况,在google逛了一大圈,试着好几种方法居然不行,????,特别是老看到The operation couldn’t be completed. (NSURLErrorDomain error -1012.),头都大了。。。

五、直到后来找到了一种解决方法...

步骤1:获取到站点的证书

我们可以使用以下openssl命令来获取到服务器的公开二进制证书(以google为例)

openssl s_client -connect www.google.com:443 </dev/null 2>/dev/null | openssl x509 -outform DER > https.cer

冒号中的为命令主要部分。该条命令将会在当前路径下,形成google.com站点的公开二进制证书,命名为https.cer。您可以将www.google.com 替换成您自己的站点以此来获取您自己站点的https.cer。

步骤2:导入到Xcode中

直接将https.cer放到资源目录中就好了,让我们可以通过pathForResource:获取到证书,就OK了。

https

步骤3:代码导入

NSString *urlString = @"https://www.example.com/app/publicRequest";
NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"https" ofType:@"cer"];
NSData * certData =[NSData dataWithContentsOfFile:cerPath];
NSSet * certSet = [[NSSet alloc] initWithObjects:certData, nil];
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
// 是否允许,NO-- 不允许无效的证书
[securityPolicy setAllowInvalidCertificates:YES];
// 设置证书
[securityPolicy setPinnedCertificates:certSet];
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.securityPolicy = securityPolicy;
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
// request
[manager GET:urlString parameters:nil progress:^(NSProgress * progress){
} success:^(NSURLSessionDataTask *task, id responseObject) {
    NSArray * array = [NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONReadingMutableLeaves error:nil];
    NSLog(@"OK === %@",array);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
    NSLog(@"error ==%@",error.description);
}];

然后,就OK了。

六、注意下服务器端两种证书的区别

  • 第一种是创建证书请求,然后到权威机构认证,随之配置到服务器;
  • 第二种是自建证书,需要自己配置给服务器。

使用第一种还是好一点的,至少在我们 app 端不需要为ATS做过多的适配。但是如何才能知道一个HTTPS服务器是符合ATS特性中的要求的呢?使用nscurl命令:

nscurl --ats-diagnostics --verbose https://example.com

下面是测百度时,返回的某一段,结论是OK的,里面有很多测试的情况,我们可以逐一观察是否正确。

TLSv1.0 with PFS disabled and insecure HTTP allowed
ATS Dictionary:
{
    NSExceptionDomains =     {
        "www.baidu.com" =         {
            NSExceptionAllowsInsecureHTTPLoads = true;
            NSExceptionMinimumTLSVersion = "TLSv1.0";
            NSExceptionRequiresForwardSecrecy = false;
        };
    };
}
Result : PASS

 

转载于:https://my.oschina.net/u/2973131/blog/777034

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值