iOS https双向认证项目适配详解

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

20160310160503593.jpeg

20160310160519781-2.jpeg
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容