苦逼的我调试AFNetworking发送https请求的bug ---------调试了一个上午,终于解决了
证书信任的过程:
证书的信任是通过代理的方式进行信任(NSURLConnection和NSURLSession两种方式进行信任)
客户端信任证书的过程:
1.当客户端要访问服务器的时候,服务器向客户端发送受保护的信任证书
2.客户端判断是否对客户端发送的证书进行信任,,
3.如果信任.则客户端会安装公钥在客户端,而服务器就拥有受保护证书的密钥,每一次向服务器请求数据的时候,服务器会先将要发送的数据进行密钥加密,客户端对所传数据通过公钥解密
下面分享一下自己的经验:
一开始请求数据先要信任证书,然后才能请求数据,不过对于百度,apple官网这样的大网站就不需要信任证书了,
只需要信任一次服务器证书
在信任证书的时候有两种方式:
大家熟知的有NSURLSession和NSURLConnection两种信任证书的方式,我会重点将第三种:
如果不信任证书的话会报下面的错误:
// NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9843)
// 原因:没有信任证书
1.先懒加载全局的会话:
@property (nonatomic, strong) NSURLSession *session;
- (NSURLSession *)session {
if (_session == nil) {
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
_session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
}
return _session;
}
2.发起数据任务
// url
NSURL *url = [NSURL URLWithString:@"https://域名"];
// 发起数据任务
[[self.session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"%@---%@",response,[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
}] resume];
3.代理方法中实现证书的信任-----------
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * __nullable credential))completionHandler {
/*
:
Host:mail.itcast.cn,
Server:https,
Auth-Scheme:NSURLAuthenticationMethodServerTrust,
*/
// 判断是否是信任服务器证书
if(challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) {
// 告诉服务器,客户端信任证书
// 创建凭据对象
NSURLCredential *credntial = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
// 通过completionHandler告诉服务器信任证书
completionHandler(NSURLSessionAuthChallengeUseCredential,credntial);
}
NSLog(@"protectionSpace = %@",challenge.protectionSpace);
}
1.发送请求:
// url
NSURL *url = [NSURL URLWithString:@"https://mail.itcast.cn"];
// request
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// 发送请求
[NSURLConnection connectionWithRequest:request delegate:self];
2.信任证书
#pragma mark - NSURLConnectionDataDelegate 代理方法
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
// 判断是否是信任服务器证书
if(challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) {
// 告诉服务器,客户端信任证书
// 创建凭据对象
NSURLCredential *credntial = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
// 告诉服务器信任证书
[challenge.sender useCredential:credntial forAuthenticationChallenge:challenge];
}
}
3.获取请求到的数据
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
NSLog(@"data = %@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
}
先上代码:
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.securityPolicy.validatesDomainName = NO;
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
// manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript",@"text/html", nil];
[manager GET:@"https://域名" parameters:nil progress:^(NSProgress * _Nonnull downloadProgress) {
NSLog(@"%@",downloadProgress);
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"%@---%@",[responseObject class],responseObject);
NSLog(@"%@",[[NSString alloc]initWithData:responseObject encoding:NSUTF8StringEncoding]);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"%@",error);
}];
注意解释遇到的坑:
1.https的协议直接请求的时候会报下面的错:
NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9843)
2.设置属性
不能将validatesDomainName设置为NO,当设置为NO以后可以发出任意的请求,所以要将其设置为YES(默认是YES)-----重点
manager.securityPolicy.validatesDomainName =YES;
AFNetworking 的安全相关设定放在 AFSecurityPolicy,它定义了三种 SSL Pinning Mode:
那要选用何种模式比较好呢?
AFSSLPinningModeCertificate
比较安全但也比较麻烦,它会比对你打包的凭证跟伺服器的凭证是否一致。因为你的凭证是跟 APP 一起打包的,这也就代表说如果你的凭证过期了或是变动了,你就得出一版新的 APP 而且旧版 APP 的凭证就失效了。你也可以在每次 APP 启动时,就自动连到某个伺服器下载最新的凭证,不过此时这个下载连线就会是有风险的。
AFSSLPinningModePublicKey 则是只有比对凭证裡的 public key,所以即使伺服器凭证有所变动,只要 public key 不变,就能通过验证。
所以如果你能确保每个使用者总是使用最新版本的 APP(例如是公司企业内部专用的),那就可以考虑 AFSSLPinningModeCertificate,否则的话选择 AFSSLPinningModePublicKey 是比较实际的作法。
解决了证书信任的问题,也就是说设置上面的属性以后就可以信任证书了
但是问题没有解决:越到了经常遇到的问题,就是请求的数据打印有问题,
Error Domain=com.alamofire.error.serialization.response Code=-1016 "Request failed: unacceptable content-type: text/html"
原因:
这是因为 AFNetworking默认把响应结果当成json来处理,(默认manager.responseSerializer = [AFJSONResponseSerializer serializer]) ,很显然,我们请求的百度首页 返回的并不是一个json文本,而是一个html网页,但是AFNetworking并不知道,它坚信请求的结果就是一个json文本!然后固执地以json的形式去解析,显然没办法把一个网页解析成一个字典或者数组,所以产生了上述错误.
然而,我们期望它能够正确地处理这个情形,而不是提示一个错误.
这时候 你必须告诉AFNetworking:别把这个网页当json来处理!
解决办法:
只需要在发送请求前加入:manager.responseSerializer = [AFHTTPResponseSerializer serializer]
以上步骤就可以实现https证书的信任和正确的获取例如baidu.com首页的html的源码...
附加说明:
网页上加载的数据本质是字符串,我们将获取到的网页数据通过二进制转字符串的形式可以讲网页数据进行打印:
NSLog(@"%@",[[NSStringalloc]initWithData:responseObjectencoding:NSUTF8StringEncoding]);
有了上面的解决办法是不是不知道如何获取凭证能,别急,下面添加如何获取制定域名的凭证:
参考链接:
http://nelson.logdown.com/posts/2015/04/29/how-to-properly-setup-afnetworking-security-connection/
取得安全凭证
1. 确认有使用安全连线
如果你跟远端伺服器是透过 HTTP 连线,那就不是安全连线,如果是 HTTPS 那就是安全连线。
2. 准备好网站的安全凭证
接下来我们需要凭证档(Certification file),它的副档名是 .cer,你可以跟你们的网站管理员询问,通常他们都知道怎麽拿到这个档桉。
如果你的网站管理员没有 .cer 档,只有 .crt 档,那你可以透过以下这行指令转档,要注意的是它是採用 DER 编码格式(请自行将 myWebsite 替换成你想要的名字):
openssl x509 -in myWebsite.crt -out myWebsite.cer -outform der
如果很不幸的,你的网站管理员连 .crt 档都没有,那你也可以使用下列这一整行指令从你们的网站取得凭证(请自行将 www.mywebsite.com 替换成你们的网址):
openssl s_client -connect www.mywebsite.com:443 /dev/null | openssl x509 -outform DER > myWebsite.cer
现在你有一个凭证档了。
3. 将凭证加入你的专桉
将你的凭证拖拉放到 Xcode 专桉底下,记得要把 Copy items if needed 跟 Add to targets 打勾