You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

React Native生产构建中合规处理自签名SSL证书的方案咨询

React Native生产构建中合规处理自签名SSL证书的方案咨询

嗨,针对你在React Native生产构建里处理自签名SSL证书的问题,我结合App Store和Google Play的合规要求,给你整理下靠谱的方案和踩坑提醒:

核心前提:绝对不能全局禁用SSL验证

首先得明确:你之前尝试的disableAllSecurity: true、信任所有证书的原生模块,这些完全过不了应用商店审核,而且会带来极大的安全风险,生产环境绝对不能用。合规的核心是「最小权限原则」——只信任你需要连接的那台设备的自签名证书,而非所有未知证书。

iOS端合规实现方案

方案1:基于react-native-ssl-pinning的正确配置

不要用disableAllSecurity,而是指定仅信任你的设备证书:

  1. 把设备的自签名证书导出为.cer.pem格式,添加到Xcode项目中,确保在Build Phases > Copy Bundle Resources里包含该证书。
  2. 在React Native代码中配置证书白名单:
import { fetch } from 'react-native-ssl-pinning';

async function fetchDeviceData() {
  try {
    const response = await fetch('https://your-device-ip:port/api/data', {
      method: 'GET',
      sslPinning: {
        certs: ['your-device-cert'], // 对应你添加到项目的证书文件名(不带后缀)
      },
    });
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('请求失败:', error);
  }
}

方案2:自定义URLSessionDelegate(原生层)

如果需要更灵活的控制,可以写自定义原生模块,但要严格做证书匹配:

func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
    guard let serverTrust = challenge.protectionSpace.serverTrust else {
        completionHandler(.cancelAuthenticationChallenge, nil)
        return
    }

    // 加载本地打包的证书
    guard let localCertPath = Bundle.main.path(forResource: "your-device-cert", ofType: "cer"),
          let localCertData = try? Data(contentsOf: URL(fileURLWithPath: localCertPath)),
          let localCert = SecCertificateCreateWithData(nil, localCertData as CFData) else {
        completionHandler(.cancelAuthenticationChallenge, nil)
        return
    }

    // 仅将本地证书设为信任锚点
    let trustAnchors = [localCert] as CFArray
    SecTrustSetAnchorCertificates(serverTrust, trustAnchors)
    SecTrustSetAnchorCertificatesOnly(serverTrust, true)

    // 验证服务器证书是否匹配
    var trustResult: SecTrustResultType = .invalid
    SecTrustEvaluate(serverTrust, &trustResult)
    if trustResult == .unspecified || trustResult == .proceed {
        let credential = URLCredential(trust: serverTrust)
        completionHandler(.useCredential, credential)
    } else {
        completionHandler(.cancelAuthenticationChallenge, nil)
    }
}

然后通过React Native桥接,让JS层调用这个自定义的网络请求工具。

Android端合规实现方案

方案1:react-native-ssl-pinning的Android配置

  1. 把设备证书导出为.pem格式,放到android/app/src/main/res/raw/目录(没有raw目录就新建)。
  2. JS层的配置和iOS端一致,插件会自动读取Android raw目录下的证书。

方案2:自定义OkHttpClient(原生层)

React Native默认用OkHttp做网络请求,我们可以替换为仅信任指定证书的OkHttpClient:

import android.content.Context
import java.io.InputStream
import java.security.KeyStore
import java.security.cert.CertificateFactory
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManagerFactory
import javax.net.ssl.X509TrustManager

class CustomSSLClient(private val context: Context) {
    fun getCustomOkHttpClient(): okhttp3.OkHttpClient {
        // 加载本地证书
        val certInputStream: InputStream = context.resources.openRawResource(R.raw.your_device_cert)
        val certificateFactory = CertificateFactory.getInstance("X.509")
        val certificate = certificateFactory.generateCertificate(certInputStream)

        // 初始化KeyStore
        val keyStore = KeyStore.getInstance(KeyStore.getDefaultType())
        keyStore.load(null)
        keyStore.setCertificateEntry("device-cert", certificate)

        // 初始化TrustManager
        val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
        trustManagerFactory.init(keyStore)
        val trustManagers = trustManagerFactory.trustManagers
        val x509TrustManager = trustManagers[0] as X509TrustManager

        // 构建SSLContext
        val sslContext = SSLContext.getInstance("TLS")
        sslContext.init(null, arrayOf(x509TrustManager), java.security.SecureRandom())

        // 构建自定义OkHttpClient
        return okhttp3.OkHttpClient.Builder()
            .sslSocketFactory(sslContext.socketFactory, x509TrustManager)
            .build()
    }
}

然后在React Native的MainApplication中替换默认的OkHttpClient。

WebView的正确处理方式

你之前用的mixedContentMode="always"是错误的,生产环境不能这么用。正确做法是给WebView配置自定义客户端,仅信任目标证书:

iOS端(WKWebView)

给WKWebView配置带自定义Delegate的URLSession,复用前面写的urlSession(_:didReceive:completionHandler:)逻辑,确保WebView的请求也只验证指定证书。

Android端

自定义WebViewClient,在证书验证时对比本地证书:

webView.webViewClient = object : WebViewClient() {
    override fun onReceivedSslError(view: WebView?, handler: SslErrorHandler?, error: SslError?) {
        error?.let { sslError ->
            val localCert = loadLocalCertificate(context) // 自己实现加载本地证书的方法
            if (isCertificateMatch(sslError.certificate, localCert)) {
                handler?.proceed() // 证书匹配则允许继续
            } else {
                handler?.cancel() // 不匹配则拒绝
            }
        } ?: handler?.cancel()
    }

    // 实现证书对比逻辑
    private fun isCertificateMatch(serverCert: android.net.http.SslCertificate?, localCert: java.security.cert.X509Certificate?): Boolean {
        if (serverCert == null || localCert == null) return false
        // 对比证书的公钥或指纹,这里简化处理,实际要做更严谨的校验
        val serverCertPem = serverCert.publicKey?.encoded?.toHexString()
        val localCertPem = localCert.publicKey.encoded.toHexString()
        return serverCertPem == localCertPem
    }
}

应用商店审核关键注意事项

  1. 必须在应用描述/隐私政策中说明:明确提到应用会连接使用自签名证书的特定设备,解释原因(比如设备是本地局域网设备,无法获取公共CA证书)。
  2. 绝对不能留后门:任何全局禁用SSL验证的代码,哪怕是注释掉的,都要删掉,审核时可能会被扫描到。
  3. 优先用公共CA证书:如果你的设备支持,尽量换成Let's Encrypt这类公共信任的免费证书,这是最省心、最合规的方案,不需要做任何特殊配置。

如果还有具体的实现细节卡壳,比如证书导出、桥接模块的写法,可以再细化问我~

火山引擎 最新活动