由于苹果规定2017年1月1日以后,所有APP都要使用HTTPS进行网络请求,否则无法上架,因此研究了一下在iOS中使用HTTPS请求的实现。相信大家对HTTPS都或多或少有些了解,这里我就不再介绍了,主要功能就是将传输的报文进行加密,提高安全性。
1、证书准备
证书分为两种,一种是花钱向认证的机构购买的证书,服务端如果使用的是这类证书的话,那一般客户端不需要做什么,用HTTPS进行请求就行了,苹果内置了那些受信任的根证书的。另一种是自己制作的证书,使用这类证书的话是不受信任的(当然也不用花钱买),因此需要我们在代码中将该证书设置为信任证书。
我这边使用的是xca来制作了根证书,制作流程请参考
,由于xca无法导出.jsk的后缀,因此我们只要制作完根证书后以.p12的格式导出就行了,之后的证书制作由命令行来完成。自制一个批处理文件,添加如下命令:
set ip=%1%
md %ip%
keytool -importkeystore -srckeystore
ca.p12
-srcstoretype PKCS12 -srcstorepass
123456
-destkeystore ca.jks -deststoretype JKS -deststorepass
123456
keytool -genkeypair -alias server-%ip% -keyalg RSA -keystore ca.jks -storepass
123456
-keypass
123456
-validity 3650 -dname "CN=%ip%, OU=ly, O=hik, L=hz, ST=zj, C=cn"
keytool -certreq -alias server-%ip% -storepass
123456
-file %ip%\server-%ip%.certreq -keystore ca.jks
keytool -gencert -alias ca -storepass
123456
-infile %ip%\server-%ip%.certreq -outfile %ip%\server-%ip%.cer -validity 3650 -keystore ca.jks
keytool -importcert -trustcacerts -storepass
123456
-alias server-%ip% -file %ip%\server-%ip%.cer -keystore ca.jks
keytool -delete -keystore ca.jks -alias ca -storepass
123456
将上面加粗的ca.p12改成你导出的.p12文件的名称,123456改为你创建证书的密码。
然后在文件夹空白处按住ctrl+shift点击右键,选择在此处打开命令窗口,在命令窗口中输入“start.bat ip/域名”来执行批处理文件,其中start.bat是添加了上述命令的批处理文件,ip/域名即你服务器的ip或者域名。执行成功后会生成一个.jks文件和一个以你的ip或域名命名的文件夹,文件夹中有一个.cer的证书,这边的.jks文件将在服务端使用.cer文件将在客户端使用,到这里证书的准备工作就完成了。
2、服务端配置
由于我不做服务端好多年,只会使用Tomcat,所以这边只讲下Tomcat的配置方法,使用其他服务器的同学请自行查找设置方法。
打开tomcat/conf目录下的server.xml文件将HTTPS的配置打开,并进行如下配置:
keystoreFile是你.jks文件放置的目录,keystorePass是你制作证书时设置的密码,netZone填写你的ip或域名。注意苹果要求协议要TLSv1.2以上。
3、iOS端配置
首先把前面生成的.cer文件添加到项目中,注意在添加的时候选择要添加的targets。
1.使用NSURLSession进行请求
(void)sessionBtnClicked {
NSString *urlString = @”https://10.20.129.25:8443/dreamVideo/restful/show“;
NSURL *url = [NSURL URLWithString:urlString];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:10.0f];
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request];
[task resume];
}(void)URLSession:(NSURLSession )session dataTask:(NSURLSessionDataTask )dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
NSLog(@”接收到服务器响应”);
//注意:这里需要使用completionHandler回调告诉系统应该如何处理服务器返回的数据
//默认是取消
/**
NSURLSessionResponseCancel = 0, 默认的处理方式,取消
NSURLSessionResponseAllow = 1, 接收服务器返回的数据
NSURLSessionResponseBecomeDownload = 2, 变成一个下载请求
NSURLSessionResponseBecomeStream 变成一个流
*/
completionHandler(NSURLSessionResponseAllow);
}(void)URLSession:(NSURLSession )session dataTask:(NSURLSessionDataTask )dataTask
didReceiveData:(NSData *)data {
NSLog(@”获取到服务段数据”);
NSLog(@”%@”,[self jsonToDictionary:data]);
}(void)URLSession:(NSURLSession )session task:(NSURLSessionTask )task
didCompleteWithError:(nullable NSError *)error {
NSLog(@”请求完成”);
}(void)URLSession:(NSURLSession )session didReceiveChallenge:(NSURLAuthenticationChallenge )challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler {
NSLog(@”证书认证”);
if ([[[challenge protectionSpace] authenticationMethod] isEqualToString: NSURLAuthenticationMethodServerTrust]) {
do
{
SecTrustRef serverTrust = [[challenge protectionSpace] serverTrust];
NSCAssert(serverTrust != nil, @”serverTrust is nil”);
if(nil == serverTrust)
break; /* failed */
/**
* 导入多张CA证书(Certification Authority,支持SSL证书以及自签名的CA),请替换掉你的证书名称
*/
NSString *cerPath = [[NSBundle mainBundle] pathForResource:@”ca” ofType:@”cer”];//自签名证书
NSData* caCert = [NSData dataWithContentsOfFile:cerPath];NSCAssert(caCert != nil, @"caCert is nil"); if(nil == caCert) break; /* failed */ SecCertificateRef caRef = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)caCert); NSCAssert(caRef != nil, @"caRef is nil"); if(nil == caRef) break; /* failed */ //可以添加多张证书 NSArray *caArray = @[(__bridge id)(caRef)]; NSCAssert(caArray != nil, @"caArray is nil"); if(nil == caArray) break; /* failed */ //将读取的证书设置为服务端帧数的根证书 OSStatus status = SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)caArray); NSCAssert(errSecSuccess == status, @"SecTrustSetAnchorCertificates failed"); if(!(errSecSuccess == status)) break; /* failed */ SecTrustResultType result = -1; //通过本地导入的证书来验证服务器的证书是否可信 status = SecTrustEvaluate(serverTrust, &result); if(!(errSecSuccess == status)) break; /* failed */ NSLog(@"stutas:%d",(int)status); NSLog(@"Result: %d", result); BOOL allowConnect = (result == kSecTrustResultUnspecified) || (result == kSecTrustResultProceed); if (allowConnect) { NSLog(@"success"); }else { NSLog(@"error"); } /* kSecTrustResultUnspecified and kSecTrustResultProceed are success */ if(! allowConnect) { break; /* failed */ }
if 0
/* Treat kSecTrustResultConfirm and kSecTrustResultRecoverableTrustFailure as success */
/* since the user will likely tap-through to see the dancing bunnies */
if(result == kSecTrustResultDeny || result == kSecTrustResultFatalTrustFailure || result == kSecTrustResultOtherError)
break; /* failed to trust cert (good in this case) */
endif
// The only good exit point
NSLog(@"信任该证书");
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
return [[challenge sender] useCredential: credential
forAuthenticationChallenge: challenge];
}
while(0);
}
// Bad dog
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge,credential);
return [[challenge sender] cancelAuthenticationChallenge: challenge];
}
- (NSDictionary )jsonToDictionary:(NSData )jsonData {
NSError *jsonError;
NSDictionary *resultDic = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableLeaves error:&jsonError];
return resultDic;
}即可成功请求到服务端。用SecTrustSetAnchorCertificates设置可信任证书列表后就只会在设置的列表中进行验证,会屏蔽掉系统原本的信任列表,要使系统的继续起作用只要调用SecTrustSetAnchorCertificates方法,第二个参数设置成NO即可。
2.使用AFNetworking进行请求
AFNetworking首先需要配置AFSecurityPolicy类,AFSecurityPolicy类封装了证书校验的过程。
/**
AFSecurityPolicy分三种验证模式:
AFSSLPinningModeNone:只是验证证书是否在信任列表中
AFSSLPinningModeCertificate:该模式会验证证书是否在信任列表中,然后再对比服务端证书和客户端证书是否一致
AFSSLPinningModePublicKey:只验证服务端证书与客户端证书的公钥是否一致
*/
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
securityPolicy.allowInvalidCertificates = YES;//是否允许使用自签名证书
securityPolicy.validatesDomainName = NO;//是否需要验证域名,默认YES
AFHTTPSessionManager *_manager = [AFHTTPSessionManager manager];
_manager.responseSerializer = [AFHTTPResponseSerializer serializer];
_manager.securityPolicy = securityPolicy;
//设置超时
[_manager.requestSerializer willChangeValueForKey:@"timeoutinterval"];
_manager.requestSerializer.timeoutInterval = 20.f;
[_manager.requestSerializer didChangeValueForKey:@"timeoutinterval"];
_manager.requestSerializer.cachePolicy = NSURLRequestReloadIgnoringCacheData;
_manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/xml",@"text/xml",@"text/plain",@"application/json",nil];
__weak typeof(self) weakSelf = self;
[_manager setSessionDidReceiveAuthenticationChallengeBlock:^NSURLSessionAuthChallengeDisposition(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential *__autoreleasing *_credential) {
SecTrustRef serverTrust = [[challenge protectionSpace] serverTrust];
/**
* 导入多张CA证书
*/
NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"ca" ofType:@"cer"];//自签名证书
NSData* caCert = [NSData dataWithContentsOfFile:cerPath];
NSArray *cerArray = @[caCert];
weakSelf.manager.securityPolicy.pinnedCertificates = cerArray;
SecCertificateRef caRef = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)caCert);
NSCAssert(caRef != nil, @"caRef is nil");
NSArray *caArray = @[(__bridge id)(caRef)];
NSCAssert(caArray != nil, @"caArray is nil");
OSStatus status = SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)caArray);
SecTrustSetAnchorCertificatesOnly(serverTrust,NO);
NSCAssert(errSecSuccess == status, @"SecTrustSetAnchorCertificates failed");
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 {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
return disposition;
}];
AFN的封装
/* 在使用的时候的调用方式/
NSString *urlString = @"https://10.20.129.25:8443/dreamVideo/restful/show";
HttpManager *httpManager = [HttpManager shareHttpManager];
[httpManager post:urlString withParameters:nil success:^(NSURLSessionDataTask *task, id responseObject) {
NSLog(@"success");
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(@"failure");
}];
import “HttpManager.h”
@interface HttpManager : NSObject
@property(nonatomic,copy)NSString *etag;
+(instancetype)shareHttpManager;
//https访问
-(void)post:(NSString )url withParameters:(id)parameters success:(void (^)(NSURLSessionDataTask _Nonnull task, id _Nullable responseObject))success failure:(void (^)(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error))failure;
import “HttpManager.h”
#import "HttpManager.h"
#import "AFHTTPSessionManager.h"
@interface HttpManager()
@property(nonatomic,retain)AFHTTPSessionManager *manager;
@end
@implementation HttpManager
+(instancetype)shareHttpManager{
static dispatch_once_t onece = 0;
static HttpManager *httpManager = nil;
dispatch_once(&onece, ^(void){
httpManager = [[self alloc]init];
});
return httpManager;
}
//https访问
-(void)post:(NSString *)url withParameters:(id)parameters success:(void (^)(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject))success failure:(void (^)(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error))failure {
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
// securityPolicy = [AFSecurityPolicy defaultPolicy];
securityPolicy.allowInvalidCertificates = YES;
securityPolicy.validatesDomainName = NO;
_manager = [AFHTTPSessionManager manager];
_manager.responseSerializer = [AFHTTPResponseSerializer serializer];
_manager.securityPolicy = securityPolicy;
//设置超时时间
[_manager.requestSerializer willChangeValueForKey:@"timeoutinterval"];
_manager.requestSerializer.timeoutInterval = 20.f;
[_manager.requestSerializer didChangeValueForKey:@"timeoutinterval"];
_manager.requestSerializer.cachePolicy = NSURLRequestReloadIgnoringCacheData;
// if (_etag) {
// [_manager.requestSerializer setValue:_etag forHTTPHeaderField:@"If-None-Match"];
// } else {
// [_manager.requestSerializer setValue:@"bb" forHTTPHeaderField:@"If-None-Match"];
// }
_manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/xml",@"text/xml",@"text/plain",@"application/json",nil];
__weak typeof(self) weakSelf = self;
[_manager setSessionDidReceiveAuthenticationChallengeBlock:^NSURLSessionAuthChallengeDisposition(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential *__autoreleasing *_credential) {
SecTrustRef serverTrust = [[challenge protectionSpace] serverTrust];
/**
* 导入多张CA证书(Certification Authority,支持SSL证书以及自签名的CA),请替换掉你的证书名称
*/
NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"ca" ofType:@"cer"];//自签名证书
NSData* caCert = [NSData dataWithContentsOfFile:cerPath];
NSArray *cerArray = @[caCert];
weakSelf.manager.securityPolicy.pinnedCertificates = cerArray;
SecCertificateRef caRef = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)caCert);
NSCAssert(caRef != nil, @"caRef is nil");
NSArray *caArray = @[(__bridge id)(caRef)];
NSCAssert(caArray != nil, @"caArray is nil");
OSStatus status = SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)caArray);
SecTrustSetAnchorCertificatesOnly(serverTrust,NO);
NSCAssert(errSecSuccess == status, @"SecTrustSetAnchorCertificates failed");
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 {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
return disposition;
}];
[_manager POST:url parameters:parameters success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response;
NSDictionary *headDic = response.allHeaderFields;
NSInteger code = response.statusCode;
NSLog(@"response statusCode is %zd",code);
// NSString *etag = headDic[@"Etag"];
// if (etag) {
// _etag = etag;
// }
NSLog(@"%@",[[NSString alloc]initWithData:responseObject encoding:NSUTF8StringEncoding]);
NSDictionary *responseDic = [self jsonToDictionary:[[NSString alloc]initWithData:responseObject encoding:NSUTF8StringEncoding]];
success(task,responseObject);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response;
NSDictionary *headDic = response.allHeaderFields;
NSInteger code = response.statusCode;
NSLog(@"response statusCode is %zd",code);
failure(task,error);
}];
}
- (NSDictionary *)jsonToDictionary:(NSString *)jsonString {
NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
NSError *jsonError;
NSDictionary *resultDic = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableLeaves error:&jsonError];
return resultDic;
}
@end