https双向认证
项目中要求使用https双向认证,提高数据传输安全性,之前用的单向认证只验证了服务端提供的证书和客户端内置的服务端证书是否一致,现在服务端需要验证客户端证书,客户端也验证服务端证书
-
afnetworking中使用https双向认证;在
setSessionDidReceiveAuthenticationChallengeBlock
block中,接收到服务端请求后,需要客户端提供证书+ (void)setupSessionManager:(AFHTTPSessionManager *)manager { __weak typeof(manager) weakManager = manager; [manager setSessionDidReceiveAuthenticationChallengeBlock:^NSURLSessionAuthChallengeDisposition(NSURLSession*session, NSURLAuthenticationChallenge *challenge, NSURLCredential *__autoreleasing*_credential) { NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling; __autoreleasing NSURLCredential *credential =nil; if([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {//验证服务器证书 if([weakManager.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 ([self 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; }]; } + (BOOL)extractIdentity:(SecIdentityRef*)outIdentity andTrust:(SecTrustRef *)outTrust fromPKCS12Data:(NSData *)inPKCS12Data { OSStatus securityError = errSecSuccess; //client certificate password 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; }
-
wkwebview中使用https双向认证;wkwebview也有类似的回调方法,处理方法和上面一致;
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler { NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling; __autoreleasing NSURLCredential *credential =nil; if([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { if([self 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 ([self 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; } } } completionHandler(disposition, credential); } - (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); // 评估当前serverTrust是否可信任 SecTrustResultType result; SecTrustEvaluate(serverTrust, &result); if (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed) { return YES; } return NO; } - (BOOL)extractIdentity:(SecIdentityRef*)outIdentity andTrust:(SecTrustRef *)outTrust fromPKCS12Data:(NSData *)inPKCS12Data { OSStatus securityError = errSecSuccess; //client certificate password NSDictionary*optionsDictionary = [NSDictionary dictionaryWithObject:@"Az.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; }
-
httpsurlconnection使用https双向认证;对httpsurlconnection进行设置,绑定客户端证书;
private static void setCertificate(HttpsURLConnection connection) { InputStream ksIn = null; try { // 服务器端需要验证的客户端证书 KeyStore trustStore = KeyStore.getInstance("BKS"); ksIn = MyApplication.getInstance().getAssets().open("client.bks"); keyStore.load(ksIn, "123456".toCharArray()); SSLContext sslContext = SSLContext.getInstance("TLS"); KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("X509"); keyManagerFactory.init(keyStore, "123456".toCharArray()); sslContext.init(keyManagerFactory.getKeyManagers(), new TrustManager[]{new TrustAllServerCerts()}, new SecureRandom());//trustManagerFactory.getTrustManagers() connection.setSSLSocketFactory(sslContext.getSocketFactory()); connection.setHostnameVerifier(new HostnameVerifier() { @Override public boolean verify(String s, SSLSession sslSession) { return true; } }); } catch (IOException | KeyStoreException | NoSuchAlgorithmException | KeyManagementException | UnrecoverableKeyException | CertificateException e) { e.printStackTrace(); } finally { try { ksIn.close(); } catch (IOException e) { e.printStackTrace(); } } } private static class TrustAllServerCerts implements X509TrustManager { // 默认的下面3个接口都会抛出一个异常,这里直接去掉异常,就是客户端忽略验证服务器端的验证信息直接通过 @Override public void checkClientTrusted(X509Certificate[] chain, String authType) { Log.e("networkUtil","checkClientTrusted"); } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) { Log.e("networkUtil","checkServerTrusted"); } @Override public X509Certificate[] getAcceptedIssuers() { Log.e("networkUtil","getAcceptedIssuers"); return new X509Certificate[]{}; } }
-
webview使用https双向认证;类似httpsurlconnection处理方法,在webviewclient的
onReceivedClientCertRequest
回调方法中,提供bks格式的客户端证书;@Override public void onReceivedClientCertRequest(WebView webView, ClientCertRequest clientCertRequest) { // super.onReceivedClientCertRequest(webView, clientCertRequest); InputStream certificateFileStream = null; try { PrivateKey privateKey = null; X509Certificate[] certificates = null; // certificateFileStream = MyApplication.getInstance().getAssets().open("client.p12");//不支持p12证书??? certificateFileStream = MyApplication.getInstance().getAssets().open("client.bks"); KeyStore keyStore = KeyStore.getInstance("BKS"); String password = "Az.123456"; keyStore.load(certificateFileStream, password.toCharArray()); Enumeration<String> aliases = keyStore.aliases(); String alias = aliases.nextElement(); Key key = keyStore.getKey(alias, password.toCharArray()); if (key instanceof PrivateKey) { privateKey = (PrivateKey) key; Certificate cert = keyStore.getCertificate(alias); certificates = new X509Certificate[1]; certificates[0] = (X509Certificate) cert; } clientCertRequest.proceed(privateKey, certificates); } catch (Exception e) { e.printStackTrace(); } finally { try { certificateFileStream.close(); } catch (Exception e) { e.printStackTrace(); } } }
iOS的实现方法比较简单,按照网上写法,替换p12证书就可以了;安卓的webviewclient里面使用的双向认证证书使用p12调试一直不行,试过在shouldInterceptRequest
回调中拦截所有请求增加https证书也不行;最后试试换了bks格式的证书可以了;iOS使用p12格式,安卓使用bks格式;
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!