You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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请求,我们需要做这几件事:

  1. 把你的.p12证书添加到iOS项目中(确保在Xcode里勾选了你的target,证书会被打包进APP)
  2. 加载.p12证书,提取里面的身份信息
  3. 自定义URLSession的信任策略,同时实现证书固定(验证服务器证书和我们的.p12证书匹配)
  4. 用这个自定义的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

关键部分说明

  1. 证书加载:loadClientCertificateWithPassword:方法负责读取.p12证书文件,转换成iOS能识别的SecIdentityRef,需要传入证书的密码(如果你的.p12没有密码,可以传空字符串,但不推荐)。
  2. 证书固定:validateServerPublicKey:方法对比服务器证书的公钥和我们.p12证书里的公钥是否一致——这是比较安全的证书固定方式(比对比证书指纹更灵活,证书过期更新后只要公钥不变就不需要改代码)。
  3. 异步请求:用URLSession的异步请求替代了废弃的同步请求,避免阻塞主线程,同时用React Native的Promise回调返回结果(比传统callback更易维护)。
  4. 证书验证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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 05:25:00