在iOS开发中,使用WKWebView加载网页时可能会遇到需要进行身份认证的情况,该代码通过处理webView:didReceiveAuthenticationChallenge:completionHandler:方法来处理认证挑战。
首先,根据认证挑战的类型,判断是客户端认证还是服务器信任认证。如果是客户端认证,则从本地获取证书文件(.p12格式),将证书信息提取出来创建NSURLCredential对象,并将其传递给completionHandler回调函数。如果是服务器信任认证,则直接创建NSURLCredential对象,并将其传递给completionHandler回调函数。
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler {
NSLog(@"\n双向认证方式didReceiveAuthenticationChallenge:%@", challenge.protectionSpace.authenticationMethod);
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodClientCertificate]) {
SecTrustRef trust = NULL;
SecIdentityRef identity = NULL;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSArray *fileList = [[[NSFileManager defaultManager] contentsOfDirectoryAtPath:[NSHomeDirectory() stringByAppendingPathComponent:@"Documents"] error:nil]
pathsMatchingExtensions:[NSArray arrayWithObject:@"p12"]];
NSString *certPath1 = [NSString stringWithFormat:@"%@/%@",[paths objectAtIndex:0],[fileList objectAtIndex:0]];
if(!certPath1) {
NSLog(@"client.p12:not exist");
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
return;
}
NSData *PKCS12Data = [NSData dataWithContentsOfFile:certPath1];
if ([self extractIdentity:&identity andTrust:&trust fromPKCS12Data:PKCS12Data]) {
SecCertificateRef certificate = NULL;
SecIdentityCopyCertificate(identity, &certificate);
const void*certs[] = {certificate};
CFArrayRef certArray =CFArrayCreate(kCFAllocatorDefault, certs,1,NULL);
NSURLCredential *credential =[NSURLCredential credentialWithIdentity:identity certificates:(__bridge NSArray*)certArray persistence:NSURLCredentialPersistencePermanent];
completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
}else{
completionHandler(NSURLSessionAuthChallengeUseCredential, nil);
}
} else if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
} else {
completionHandler(NSURLSessionAuthChallengeUseCredential, nil);
}
}
在evaluateServerTrust:forDomain:方法中,根据指定的域名创建证书校验策略,并绑定到服务器的证书上。然后通过SecTrustEvaluate方法评估当前的serverTrust是否可信任。
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain {
domain = nil;
NSMutableArray *policies = [NSMutableArray array];
if (domain) {
[policies addObject:(__bridge_transfer id) SecPolicyCreateSSL(true, (__bridge CFStringRef) domain)];
} else {
[policies addObject:(__bridge_transfer id) SecPolicyCreateBasicX509()];
}
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef) policies);
SecTrustResultType result;
SecTrustEvaluate(serverTrust, &result);
if (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed) {
return YES;
}
return NO;
}
extractIdentity:andTrust:fromPKCS12Data:方法用于从PKCS12格式的数据中提取身份和信任对象。
- (BOOL)extractIdentity:(SecIdentityRef*)outIdentity andTrust:(SecTrustRef *)outTrust fromPKCS12Data:(NSData *)inPKCS12Data {
OSStatus securityError = errSecSuccess;
NSDictionary *optionsDictionary = [NSDictionary dictionaryWithObject:@"123456" 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;
}