Https IOS客户端适配包括三方面:
1、接口双向认证
2、webView双向认证
3、imageView忽略认证
直接上核心代码:
1、接口双向认证
项目中使用的是AFNetworking, HYBNetworking工具类
+ (AFHTTPSessionManager *)manager {
@synchronized (self) {
// 只要不切换baseurl,就一直使用同一个session manager
if (sg_sharedManager == nil || sg_isBaseURLChanged) {
// 开启转圈圈
[AFNetworkActivityIndicatorManager sharedManager].enabled = YES;
AFHTTPSessionManager *manager = nil;;
if ([self baseUrl] != nil) {
manager = [[AFHTTPSessionManager alloc] initWithBaseURL:[NSURL URLWithString:[self baseUrl]]];
} else {
manager = [AFHTTPSessionManager manager];
}
switch (sg_requestType) {
case kHYBRequestTypeJSON: {
//manager.requestSerializer = [AFJSONRequestSerializer serializer];
break;
}
case kHYBRequestTypePlainText: {
manager.requestSerializer = [AFHTTPRequestSerializer serializer];
break;
}
default: {
break;
}
}
switch (sg_responseType) {
case kHYBResponseTypeJSON: {
manager.responseSerializer = [AFJSONResponseSerializer serializer];
break;
}
case kHYBResponseTypeXML: {
manager.responseSerializer = [AFXMLParserResponseSerializer serializer];
break;
}
case kHYBResponseTypeData: {
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
break;
}
default: {
break;
}
}
manager.requestSerializer.stringEncoding = NSUTF8StringEncoding;
for (NSString *key in sg_httpHeaders.allKeys) {
if (sg_httpHeaders[key] != nil) {
[manager.requestSerializer setValue:sg_httpHeaders[key] forHTTPHeaderField:key];
}
}
manager.responseSerializer.acceptableContentTypes = [NSSet setWithArray:@[@"application/json",
@"text/html",
@"text/json",
@"text/javascript"]];
manager.requestSerializer.timeoutInterval = sg_timeout;
// 设置允许同时最大并发数量,过大容易出问题
manager.operationQueue.maxConcurrentOperationCount = 3;
//关闭缓存避免干扰测试
manager.requestSerializer.cachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
//基于公钥设置客服端安全策略 ssl
manager.securityPolicy = [self customSecurityPolicy];
//客服端利用p12验证服务器
[self checkCredential:manager];
sg_sharedManager = manager;
}
}
return sg_sharedManager;
}
#pragma mark - ********** SSL校验 **********
/** SSL 1.单向验证 */
+ (AFSecurityPolicy*)customSecurityPolicy {
// AFSSLPinningModeCertificate:需要客户端预先保存服务端的证书(自建证书)
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
NSString * cerPath = [[NSBundle mainBundle] pathForResource:kHttpsServiceCer ofType:@"cer"];
NSData *certData = [NSData dataWithContentsOfFile:cerPath];
NSSet *dataSet = [NSSet setWithArray:@[certData]];
// 自建证书的时候,提供相应的证书
[securityPolicy setPinnedCertificates:dataSet];
// 是否允许无效证书(自建证书)
[securityPolicy setAllowInvalidCertificates:YES];
// 是否需要验证域名
[securityPolicy setValidatesDomainName:NO];
return securityPolicy;
}
/**
客户端验证服务器信任凭证
@param manager AFURLSessionManager
*/
+ (void)checkCredential:(AFURLSessionManager *)manager
{
//为了方便测试
[manager setSessionDidBecomeInvalidBlock:^(NSURLSession * _Nonnull session, NSError * _Nonnull error) {
NSLog(@"%s error:%@",__FUNCTION__,error);
}];
wSelf(self);
__weak typeof(manager) weakManager = manager;
[manager setSessionDidReceiveAuthenticationChallengeBlock:^NSURLSessionAuthChallengeDisposition(NSURLSession*session, NSURLAuthenticationChallenge *challenge, NSURLCredential *__autoreleasing*_credential) {
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__autoreleasing NSURLCredential *credential = nil;
NSLog(@"authenticationMethod=%@",challenge.protectionSpace.authenticationMethod);
//判断当前校验的是客户端证书还是服务器证书
if([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
// 客户端的安全策略来决定是否信任该服务器;不信任,则取消请求。
//接口提示:已取消;是因为客户端设置了需要验证域名
if([weakManager.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
// 创建URL凭证
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:kHttpsClientP12 ofType:@"p12"];
NSFileManager *fileManager =[NSFileManager defaultManager];
if(![fileManager fileExistsAtPath:p12]){
NSLog(@"client.p12:not exist");
}else{
NSData *PKCS12Data = [NSData dataWithContentsOfFile:p12];
if ([wSelf 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;
}];
}
//解读p12文件信息
+ (BOOL)extractIdentity:(SecIdentityRef*)outIdentity andTrust:(SecTrustRef *)outTrust fromPKCS12Data:(NSData *)inPKCS12Data {
OSStatus securityError = errSecSuccess;
//client certificate password
NSDictionary *optionsDic = [NSDictionary dictionaryWithObject:kHttpsP12Password
forKey:(__bridge id)kSecImportExportPassphrase];
CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
securityError = SecPKCS12Import((__bridge CFDataRef)inPKCS12Data,(__bridge CFDictionaryRef)optionsDic,&items);
if(securityError == errSecSuccess) {
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;
}
2、webView双向认证
@interface AgreementController ()<UIWebViewDelegate,NSURLConnectionDataDelegate>
@property (nonatomic, strong) UIWebView *webView;
@property (nonatomic, strong) NSURL *baseUrl;
@property (nonatomic, strong) NSMutableData *data;//页面缓存数据
@end
@implementation AgreementController
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.view bringSubviewToFront:self.navBarBackgroud];
[MobClick beginLogPageView:@"AgreementVC"];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[MobClick endLogPageView:@"AgreementVC"];
}
- (NSMutableData *)data
{
if (_data == nil){
_data = [[NSMutableData alloc] init];
}
return _data;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"用户协议";
_webView = [[UIWebView alloc] initWithFrame:CGRectMake(0, 0, kDeviceWidth, kDeviceHeight)];
_webView.scalesPageToFit = YES;
_webView.delegate = self;
[_webView scalesPageToFit];
[self.view addSubview:_webView];
NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
NSString *url = [HYBNetworking absoluteUrlPath:kUrlAgreement params:params];
self.baseUrl = [NSURL URLWithString:url];
NSURLRequest *request = [NSURLRequest requestWithURL:self.baseUrl];
[NSURLConnection connectionWithRequest:request delegate:self];
}
#pragma mark - UIWebViewDelegate
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
[MBProgressHUD hideHUD];
}
- (void)webViewDidStartLoad:(UIWebView *)webView
{
[MBProgressHUD showMessage:@"正在加载中......."];
}
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
[MBProgressHUD hideHUD];
}
#pragma mark - NSURLConnectionDataDelegate
//接收到服务器返回的数据时调用
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSLog(@"接收到的数据%zd",data.length);
[self.data appendData:data];
}
//请求完毕
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(@"%s 请求完毕",__FUNCTION__);
NSString *html = [[NSString alloc] initWithData:self.data encoding:NSUTF8StringEncoding];
[self.webView loadHTMLString:html baseURL:self.baseUrl];
}
//如果返回NO,将由系统自行处理. 返回YES将会由后续的didReceiveAuthenticationChallenge处理。默认为NO
- (BOOL)connection:(NSURLConnection*)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace*)protectionSpace
{
return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}
-(void)connection:(NSURLConnection*)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge*)challenge
{
NSLog(@"%s",__FUNCTION__);
//判断是否是信任服务器证书
if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust){
//创建一个凭据对象
NSURLCredential *credential =
[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
//告诉服务器客户端信任证书
[challenge.sender useCredential:credential forAuthenticationChallenge:challenge];
}else{
[[challenge sender] cancelAuthenticationChallenge:challenge];
}
}
//允许跳过安全认证
+ (BOOL)allowsAnyHTTPSCertificateForHost:(NSString*)host
{
NSLog(@"%s",__FUNCTION__);
return YES;
}
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
NSURLCredential * credential;
assert(challenge != nil);
credential = nil;
NSLog(@"----received challenge----");
NSString *authenticationMethod = [[challenge protectionSpace] authenticationMethod];
if ([authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
NSLog(@"----server verify client----");
NSString *host = challenge.protectionSpace.host;
SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
BOOL validDomain = false;
NSMutableArray *polices = [NSMutableArray array];
if (validDomain) {
[polices addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)host)];
}else{
[polices addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
}
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)polices);
//pin mode for certificate
NSString *path = [[NSBundle mainBundle] pathForResource:kHttpsServiceCer ofType:@"cer"];
NSData *certData = [NSData dataWithContentsOfFile:path];
NSMutableArray *pinnedCerts = [NSMutableArray arrayWithObjects:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certData), nil];
SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCerts);
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
} else {
NSLog(@"----client verify server----");
SecIdentityRef identity = NULL;
SecTrustRef trust = NULL;
NSString *p12 = [[NSBundle mainBundle] pathForResource:kHttpsClientP12 ofType:@"p12"];
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:p12]) {
NSLog(@"client.p12 file not exist!");
}else{
NSData *pkcs12Data = [NSData dataWithContentsOfFile:p12];
if ([HYBNetworking 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];
}
}
}
[challenge.sender useCredential:credential forAuthenticationChallenge:challenge];
}
3、imageView忽略认证
NSURL *url = [NSURL URLWithString:model.resume.head_img_small];
UIImage *placeholder = [UIImage imageNamed:@"photo.png"];
[_headView.avator yy_setImageWithURL:url
placeholder:placeholder
options:YYWebImageOptionAllowInvalidSSLCertificates
completion:NULL];
特别说明
#define kHttpsServiceCer @"server" //服务器公钥server.cer
#define kHttpsClientP12 @"client" //服务器授权的p12:包含服务器信息+私钥
#define kHttpsP12Password @"123" //访问p12文件的密码
2、webView使用
//请求并缓存页面数据,避免重复请求接口
[NSURLConnection connectionWithRequest:request delegate:self];
//请求完毕
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(@"%s 请求完毕",__FUNCTION__);
NSString *html = [[NSString alloc] initWithData:self.data encoding:NSUTF8StringEncoding];
[self.webView loadHTMLString:html baseURL:self.baseUrl];
}
3、“已取消”问题。部署好双向认证代码后,请求接口出现:提示 “已取消”。是因为客户端设置了需要验证域名。
// 是否需要验证域名
[securityPolicy setValidatesDomainName:NO];
附加:来源http://blog.csdn.net/duanbokan/article/details/50847612