IOS开发iOS学习iOS Developer

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

2017-06-09  本文已影响484人  Hengry
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
上一篇 下一篇

猜你喜欢

热点阅读