React Native原生模块:Objective-C实现带.p12证书的HTTPS请求
如何在React Native原生模块中实现带.p12证书的HTTPS请求(证书固定)
别担心,我来一步步帮你把这个原生模块改成支持.p12证书的HTTPS请求,还加上证书固定。先从修复你现有代码的小问题开始,再逐步添加证书相关的逻辑——虽然是Objective-C,但我会把每一步讲清楚。
第一步:先修正现有代码的基础问题
你现在的代码有几个关键问题,得先搞定:
sendSynchronousRequest是iOS 9就废弃的API,苹果推荐用URLSession异步请求(同步请求会阻塞主线程,严重影响APP性能)- 方法参数定义有语法错误:
sendGetRequest:(NSString *)urllocation:(NSString *)location应该改成sendGetRequest:(NSString *)url location:(NSString *)location(你把参数名url和location连在一起了) - 方法里没有声明回调参数,根本没法把结果返回给React Native
第二步:添加.p12证书支持和证书固定
要实现带证书的HTTPS请求,我们需要做这几件事:
- 把你的.p12证书添加到iOS项目中(确保在Xcode里勾选了你的target,证书会被打包进APP)
- 加载.p12证书,提取里面的身份信息
- 自定义
URLSession的信任策略,同时实现证书固定(验证服务器证书和我们的.p12证书匹配) - 用这个自定义的
URLSession发送请求
下面是完整的MyBridge.m代码,关键部分我都加了注释:
#import "MyFirstBridge.h" #import <React/RCTLog.h> #import <Security/Security.h> @implementation MyFirstBridge RCT_EXPORT_MODULE(); // 加载.p12证书的辅助方法 - (SecIdentityRef)loadClientCertificateWithPassword:(NSString *)password { // 获取证书文件路径(假设你的证书名叫client.p12,要和你添加到项目里的文件名一致) NSString *certPath = [[NSBundle mainBundle] pathForResource:@"client" ofType:@"p12"]; NSData *certData = [NSData dataWithContentsOfFile:certPath]; if (!certData) { RCTLogError(@"Failed to load p12 certificate file"); return NULL; } // 把.p12证书转换成iOS能识别的SecIdentityRef CFStringRef passwordRef = (__bridge CFStringRef)password; const void *keys[] = {kSecImportExportPassphrase}; const void *values[] = {passwordRef}; CFDictionaryRef options = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL); CFArrayRef items = NULL; OSStatus status = SecPKCS12Import((__bridge CFDataRef)certData, options, &items); CFRelease(options); if (status != errSecSuccess || CFArrayGetCount(items) == 0) { RCTLogError(@"Failed to import p12 certificate, status code: %d", (int)status); if (items) CFRelease(items); return NULL; } CFDictionaryRef identityDict = CFArrayGetValueAtIndex(items, 0); SecIdentityRef identity = (__bridge SecIdentityRef)CFDictionaryGetValue(identityDict, kSecImportItemIdentity); CFRelease(items); return identity; } // 证书固定:验证服务器证书的公钥和我们的证书公钥是否一致 - (BOOL)validateServerPublicKey:(SecKeyRef)serverPublicKey withClientIdentity:(SecIdentityRef)clientIdentity { SecKeyRef clientPublicKey = NULL; SecIdentityCopyPublicKey(clientIdentity, &clientPublicKey); if (!clientPublicKey) { RCTLogError(@"Failed to get client public key"); return NO; } // 对比两个公钥的原始数据是否一致 NSData *serverKeyData = (__bridge_transfer NSData *)SecKeyCopyExternalRepresentation(serverPublicKey, NULL); NSData *clientKeyData = (__bridge_transfer NSData *)SecKeyCopyExternalRepresentation(clientPublicKey, NULL); BOOL isMatch = [serverKeyData isEqual:clientKeyData]; CFRelease(clientPublicKey); return isMatch; } // 导出给React Native的方法,用Promise替代传统callback(更符合RN最佳实践) RCT_EXPORT_METHOD(sendSecureGetRequest:(NSString *)urlString certificatePassword:(NSString *)password resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { // 加载.p12证书 SecIdentityRef clientIdentity = [self loadClientCertificateWithPassword:password]; if (!clientIdentity) { reject(@"CERT_LOAD_FAILED", @"Failed to load client certificate", nil); return; } // 验证URL合法性 NSURL *url = [NSURL URLWithString:urlString]; if (!url) { reject(@"INVALID_URL", @"Invalid URL string", nil); CFRelease(clientIdentity); return; } NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; [request setHTTPMethod:@"GET"]; // 配置自定义URLSession,添加证书和信任策略 NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[NSOperationQueue mainQueue]]; // 把证书身份绑定到会话,方便在delegate方法中获取 [session setValue:(__bridge id)clientIdentity forKey:@"clientIdentity"]; // 发送异步请求 NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { CFRelease(clientIdentity); // 用完证书后释放资源 if (error) { reject(@"REQUEST_FAILED", error.localizedDescription, error); return; } NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; if (httpResponse.statusCode != 200) { reject(@"HTTP_ERROR", [NSString stringWithFormat:@"HTTP status code: %ld", (long)httpResponse.statusCode], nil); return; } NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; if (!responseString) { reject(@"DATA_PARSE_FAILED", @"Failed to parse response data to string", nil); return; } resolve(responseString); }]; [task resume]; } // URLSessionDelegate方法:处理服务器证书验证和证书固定逻辑 - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler { // 只处理服务器信任类型的验证挑战 if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { SecTrustRef serverTrust = challenge.protectionSpace.serverTrust; // 获取我们绑定的客户端证书身份 SecIdentityRef clientIdentity = (__bridge SecIdentityRef)[session valueForKey:@"clientIdentity"]; if (!clientIdentity) { completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil); return; } // 先让系统验证服务器证书的合法性 SecTrustResultType trustResult; OSStatus status = SecTrustEvaluate(serverTrust, &trustResult); if (status != errSecSuccess || !(trustResult == kSecTrustResultUnspecified || trustResult == kSecTrustResultProceed)) { // 系统验证失败时,手动执行证书固定验证 SecCertificateRef serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0); SecKeyRef serverPublicKey = SecCertificateCopyPublicKey(serverCertificate); BOOL isPublicKeyMatch = [self validateServerPublicKey:serverPublicKey withClientIdentity:clientIdentity]; CFRelease(serverPublicKey); CFRelease(serverCertificate); if (!isPublicKeyMatch) { completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil); return; } } // 创建客户端证书凭证,允许请求继续 NSURLCredential *credential = [NSURLCredential credentialWithIdentity:clientIdentity certificates:nil persistence:NSURLCredentialPersistenceForSession]; completionHandler(NSURLSessionAuthChallengeUseCredential, credential); } else { // 其他类型的验证挑战,按系统默认逻辑处理 completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); } } @end
关键部分说明
- 证书加载:
loadClientCertificateWithPassword:方法负责读取.p12证书文件,转换成iOS能识别的SecIdentityRef,需要传入证书的密码(如果你的.p12没有密码,可以传空字符串,但不推荐)。 - 证书固定:
validateServerPublicKey:方法对比服务器证书的公钥和我们.p12证书里的公钥是否一致——这是比较安全的证书固定方式(比对比证书指纹更灵活,证书过期更新后只要公钥不变就不需要改代码)。 - 异步请求:用
URLSession的异步请求替代了废弃的同步请求,避免阻塞主线程,同时用React Native的Promise回调返回结果(比传统callback更易维护)。 - 证书验证Delegate:
URLSession:didReceiveChallenge:completionHandler:方法处理服务器证书的验证,同时应用我们的证书固定逻辑,只有验证通过才会继续请求。
在React Native中调用这个方法
import { NativeModules } from 'react-native'; const { MyFirstBridge } = NativeModules; // 调用示例 async function sendSecureRequest() { try { const response = await MyFirstBridge.sendSecureGetRequest( 'https://your-secure-api.com/data', 'your-p12-password' ); console.log('Response:', response); } catch (error) { console.error('Error:', error); } }
注意事项
- 确保你的.p12证书已经添加到iOS项目中,并且在Xcode的
Build Phases->Copy Bundle Resources里能看到它。 - 证书密码不要硬编码在代码里,最好从安全的地方读取(比如Keychain)。
- 如果你的API服务器使用的是CA签发的证书,代码会先让系统验证证书合法性,再执行证书固定逻辑,兼顾了兼容性和安全性。
内容的提问来源于stack exchange,提问作者errorau
相关产品推荐
相关产品推荐

