zoukankan      html  css  js  c++  java
  • iOS开发

    https相关

    自苹果宣布2017年1月1日开始强制使用https以来,htpps慢慢成为大家讨论的对象之一,不是说此前https没有出现,只是这一决策让得开发者始料未及,博主在15年的时候就做过https的接口,深知此坑之深,原因就是自身对这方面知识不了解加上网上的资料少,除此外还有博客不知对错就互相转载,导致当时网上几乎找不到能用的代码,这一点,博主说的毫不夸张。

    鉴于此,博主一直想填一下这个坑,多增加一些正确的代码,来供广大开发者使用,后来一直被搁置,经过尝试后,博主现将整理好的代码发布在这里,希望能帮到焦急寻找的开发者。

    1.先来说说老的AFNetworking2.x怎么来实现的 
    博主在网上看过几篇帖子,其中说的一些方法是正确的,但是却并不全对,由于那几篇博客几乎一样,博主不能确定最早的那篇是谁写的,所以就重新在下面说明下方法:

    1)倒入client.p12证书;

    2)在plist文件做如图配置: 
    这里写图片描述
    各个项代表的含义请看这篇博客,博主写的很详细:http://blog.csdn.net/zhouzhoujianquan/article/details/53401506 除了一些说明,对方也提供了一些代码,大家可以拿来相互印证;

    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内的设置,这是和上面一样的: 
    这里写图片描述
    各个项代表的含义请看这篇博客,博主写的很详细:http://blog.csdn.net/zhouzhoujianquan/article/details/53401506 除了一些说明,对方也提供了一些代码,大家可以拿来相互印证;

    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;
        }];
    

    关于这段代码,其实和老的AF里面修改的道理是一样的,可以看下这篇文章:http://www.jianshu.com/p/9e573607be13

    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;
    }

    你会发现http://www.jianshu.com/p/9e573607be13 这里的代码有冗余,没错,我们是要封装一下,可是要怎么封装呢?博主尝试了集中都失败了,真是百思不得解,相信主动去封装的开发者也会碰到封装后请求失败的问题,也许你成功了,但是这里需要注意一个在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

  • 相关阅读:
    洛谷P2345 奶牛集会
    洛谷P3531 [POI2012]LIT-Letters
    codevs 4163 hzwer与逆序对
    各种读入方式速度比较
    洛谷P1420 最长连号
    TCPDump:捕获并记录特定协议 / 端口
    linux下抓取网页快照
    Pro Android 4 第五章 理解Intent
    UpdatePanel和jQuery不兼容
    RAC 11.2.0.4 安装 遇到 INS-06001
  • 原文地址:https://www.cnblogs.com/isItOk/p/6582949.html
Copyright © 2011-2022 走看看