详解iOS开发 - 用AFNetworking实现https单向验证,双向验证
发布时间 - 2026-01-10 21:57:28 点击率:次自苹果宣布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
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
# ios开发
# 单向https
# ios
# https双向验证
# os
# https双向认证
# iOS9苹果将原http协议改成了https协议的方法
# IOS开发 支持https请求以及ssl证书配置详解
# iOS适配https证书问题(AFNetworking3.0为例)
# iOS之Https自签名证书认证及数据请求的封装原理
# iOS实用教程之Https双向认证详解
# 放在
# 重写
# 一个问题
# 来实现
# 网上
# 自己的
# 几篇
# 客户端
# 这是
# 这一
# 在这里
# 也会
# 是有
# 找不到
# 始料未及
# 请大家
# 说到
# 这段
# 博客
# 希望能
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
Laravel如何使用Gate和Policy进行授权?(权限控制)
Laravel怎么使用Intervention Image库处理图片上传和缩放
javascript日期怎么处理_如何格式化输出
如何用y主机助手快速搭建网站?
bing浏览器学术搜索入口_bing学术文献检索地址
Laravel的.env文件有什么用_Laravel环境变量配置与管理详解
如何实现建站之星域名转发设置?
linux写shell需要注意的问题(必看)
企业网站制作这些问题要关注
Swift开发中switch语句值绑定模式
java中使用zxing批量生成二维码立牌
Laravel的Blade指令怎么自定义_创建你自己的Laravel Blade Directives
Laravel怎么配置自定义表前缀_Laravel数据库迁移与Eloquent表名映射【步骤】
手机网站制作平台,手机靓号代理商怎么制作属于自己的手机靓号网站?
厦门模型网站设计制作公司,厦门航空飞机模型掉色怎么办?
如何在IIS中新建站点并解决端口绑定冲突?
北京网站制作的公司有哪些,北京白云观官方网站?
个人摄影网站制作流程,摄影爱好者都去什么网站?
如何基于云服务器快速搭建网站及云盘系统?
谷歌浏览器下载文件时中断怎么办 Google Chrome下载管理修复
Laravel PHP版本要求一览_Laravel各版本环境要求对照
Laravel如何生成PDF或Excel文件_Laravel文档导出工具与使用教程
为什么php本地部署后css不生效_静态资源加载失败修复技巧【技巧】
php485函数参数是什么意思_php485各参数详细说明【介绍】
Win11怎么关闭透明效果_Windows11辅助功能视觉效果设置
Bootstrap整体框架之JavaScript插件架构
Laravel如何使用Service Container和依赖注入?(代码示例)
Laravel观察者模式如何使用_Laravel Model Observer配置
如何在Tomcat中配置并部署网站项目?
JavaScript数据类型有哪些_如何准确判断一个变量的类型
html5源代码发行怎么设置权限_访问权限控制方法与实践【指南】
Android使用GridView实现日历的简单功能
详解Android——蓝牙技术 带你实现终端间数据传输
如何快速上传自定义模板至建站之星?
弹幕视频网站制作教程下载,弹幕视频网站是什么意思?
JavaScript 输出显示内容(document.write、alert、innerHTML、console.log)
Laravel如何使用Eloquent ORM进行数据库操作?(CRUD示例)
Python正则表达式进阶教程_复杂匹配与分组替换解析
Laravel Blade模板引擎语法_Laravel Blade布局继承用法
Laravel怎么配置.env环境变量_Laravel生产环境敏感数据保护与读取【方法】
智能起名网站制作软件有哪些,制作logo的软件?
如何自定义safari浏览器工具栏?个性化设置safari浏览器界面教程【技巧】
Claude怎样写约束型提示词_Claude约束提示词写法【教程】
阿里云网站搭建费用解析:服务器价格与建站成本优化指南
,网页ppt怎么弄成自己的ppt?
如何在万网主机上快速搭建网站?
Java垃圾回收器的方法和原理总结
网页制作模板网站推荐,网页设计海报之类的素材哪里好?
儿童网站界面设计图片,中国少年儿童教育网站-怎么去注册?
Laravel如何自定义分页视图?(Pagination示例)

