Https IOS客戶端適配包括三方面:
1乏盐、接口雙向認(rèn)證
2、webView雙向認(rèn)證
3屯曹、imageView忽略認(rèn)證
直接上核心代碼:
1谆刨、接口雙向認(rèn)證
項(xiàng)目中使用的是AFNetworking, HYBNetworking工具類
+ (AFHTTPSessionManager *)manager {
@synchronized (self) {
// 只要不切換baseurl导狡,就一直使用同一個(gè)session manager
if (sg_sharedManager == nil || sg_isBaseURLChanged) {
// 開啟轉(zhuǎn)圈圈
[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;
// 設(shè)置允許同時(shí)最大并發(fā)數(shù)量鉴逞,過大容易出問題
manager.operationQueue.maxConcurrentOperationCount = 3;
//關(guān)閉緩存避免干擾測(cè)試
manager.requestSerializer.cachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
//基于公鑰設(shè)置客服端安全策略 ssl
manager.securityPolicy = [self customSecurityPolicy];
//客服端利用p12驗(yàn)證服務(wù)器
[self checkCredential:manager];
sg_sharedManager = manager;
}
}
return sg_sharedManager;
}
#pragma mark - ********** SSL校驗(yàn) **********
/** SSL 1.單向驗(yàn)證 */
+ (AFSecurityPolicy*)customSecurityPolicy {
// AFSSLPinningModeCertificate:需要客戶端預(yù)先保存服務(wù)端的證書(自建證書)
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
NSString * cerPath = [[NSBundle mainBundle] pathForResource:kHttpsServiceCer ofType:@"cer"];
NSData *certData = [NSData dataWithContentsOfFile:cerPath];
NSSet *dataSet = [NSSet setWithArray:@[certData]];
// 自建證書的時(shí)候记某,提供相應(yīng)的證書
[securityPolicy setPinnedCertificates:dataSet];
// 是否允許無效證書(自建證書)
[securityPolicy setAllowInvalidCertificates:YES];
// 是否需要驗(yàn)證域名
[securityPolicy setValidatesDomainName:NO];
return securityPolicy;
}
/**
客戶端驗(yàn)證服務(wù)器信任憑證
@param manager AFURLSessionManager
*/
+ (void)checkCredential:(AFURLSessionManager *)manager
{
//為了方便測(cè)試
[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);
//判斷當(dāng)前校驗(yàn)的是客戶端證書還是服務(wù)器證書
if([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
// 客戶端的安全策略來決定是否信任該服務(wù)器;不信任华蜒,則取消請(qǐng)求辙纬。
//接口提示:已取消;是因?yàn)榭蛻舳嗽O(shè)置了需要驗(yàn)證域名
if([weakManager.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
// 創(chuàng)建URL憑證
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
if (credential) {
disposition = NSURLSessionAuthChallengeUseCredential;//使用(信任)證書
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;//默認(rèn)豁遭,忽略
}
} 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雙向認(rèn)證
@interface AgreementController ()<UIWebViewDelegate,NSURLConnectionDataDelegate>
@property (nonatomic, strong) UIWebView *webView;
@property (nonatomic, strong) NSURL *baseUrl;
@property (nonatomic, strong) NSMutableData *data;//頁面緩存數(shù)據(jù)
@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 = @"用戶協(xié)議";
_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
//接收到服務(wù)器返回的數(shù)據(jù)時(shí)調(diào)用
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSLog(@"接收到的數(shù)據(jù)%zd",data.length);
[self.data appendData:data];
}
//請(qǐng)求完畢
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(@"%s 請(qǐng)求完畢",__FUNCTION__);
NSString *html = [[NSString alloc] initWithData:self.data encoding:NSUTF8StringEncoding];
[self.webView loadHTMLString:html baseURL:self.baseUrl];
}
//如果返回NO,將由系統(tǒng)自行處理. 返回YES將會(huì)由后續(xù)的didReceiveAuthenticationChallenge處理蓖谢。默認(rèn)為NO
- (BOOL)connection:(NSURLConnection*)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace*)protectionSpace
{
return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}
-(void)connection:(NSURLConnection*)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge*)challenge
{
NSLog(@"%s",__FUNCTION__);
//判斷是否是信任服務(wù)器證書
if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust){
//創(chuàng)建一個(gè)憑據(jù)對(duì)象
NSURLCredential *credential =
[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
//告訴服務(wù)器客戶端信任證書
[challenge.sender useCredential:credential forAuthenticationChallenge:challenge];
}else{
[[challenge sender] cancelAuthenticationChallenge:challenge];
}
}
//允許跳過安全認(rèn)證
+ (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忽略認(rèn)證
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" //服務(wù)器公鑰server.cer
#define kHttpsClientP12 @"client" //服務(wù)器授權(quán)的p12:包含服務(wù)器信息+私鑰
#define kHttpsP12Password @"123" //訪問p12文件的密碼
2、webView使用
//請(qǐng)求并緩存頁面數(shù)據(jù)闪幽,避免重復(fù)請(qǐng)求接口
[NSURLConnection connectionWithRequest:request delegate:self];
//請(qǐng)求完畢
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(@"%s 請(qǐng)求完畢",__FUNCTION__);
NSString *html = [[NSString alloc] initWithData:self.data encoding:NSUTF8StringEncoding];
[self.webView loadHTMLString:html baseURL:self.baseUrl];
}
3啥辨、“已取消”問題。部署好雙向認(rèn)證代碼后盯腌,請(qǐng)求接口出現(xiàn):提示 “已取消”溉知。是因?yàn)榭蛻舳嗽O(shè)置了需要驗(yàn)證域名。
// 是否需要驗(yàn)證域名
[securityPolicy setValidatesDomainName:NO];
附加:來源http://blog.csdn.net/duanbokan/article/details/50847612
20160310160503593.jpeg
20160310160519781-2.jpeg