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

如何在含原生模块与WebView/Cordova的APP中白名单自签名SSL证书?

针对你的混合架构移动应用(原生模块 + WebView/Cordova 嵌入的HTML5),要让它顺利信任你那套已经在Web应用里用起来的自签名SSL证书,得分开处理原生HTTPS调用和WebView内的AJAX请求,下面是具体的实操方案:

一、原生模块的HTTPS调用配置

原生部分的HTTPS请求得让APP本身信任自签名证书,分iOS和Android两个平台单独处理:

iOS平台

方案1:内置证书到APP(推荐,无需用户手动操作)

把你的自签名证书(.cer或.pem格式)拖进Xcode项目,记得勾选目标APP,确保它出现在Build Phases的Copy Bundle Resources列表里。然后自定义URLSession的信任策略,验证服务器返回的证书和我们内置的是否匹配:

func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
    guard let serverTrust = challenge.protectionSpace.serverTrust else {
        completionHandler(.cancelAuthenticationChallenge, nil)
        return
    }
    // 加载APP里的自签名证书
    guard let certPath = Bundle.main.path(forResource: "your-custom-cert", ofType: "cer"),
          let certData = try? Data(contentsOf: URL(fileURLWithPath: certPath)),
          let targetCert = SecCertificateCreateWithData(nil, certData as CFData) else {
        completionHandler(.cancelAuthenticationChallenge, nil)
        return
    }
    // 把我们的证书设为信任锚点
    let trustedCerts = [targetCert]
    SecTrustSetAnchorCertificates(serverTrust, trustedCerts as CFArray)
    
    // 验证服务器证书
    var trustResult: SecTrustResultType = .invalid
    SecTrustEvaluate(serverTrust, &trustResult)
    if trustResult == .unspecified || trustResult == .proceed {
        // 证书匹配,允许连接
        let credential = URLCredential(trust: serverTrust)
        completionHandler(.useCredential, credential)
    } else {
        // 证书不匹配,拒绝连接
        completionHandler(.cancelAuthenticationChallenge, nil)
    }
}

方案2:配置ATS例外(辅助用,适配旧版iOS)

如果是iOS13以下的版本,可能需要在Info.plist里添加ATS配置,给后端域名开例外:

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSExceptionDomains</key>
    <dict>
        <key>your-backend-domain.com</key>
        <dict>
            <key>NSExceptionRequiresForwardSecrecy</key>
            <false/>
            <key>NSIncludesSubdomains</key>
            <true/>
        </dict>
    </dict>
</dict>

Android平台

方案1:内置证书到APP(推荐)

先把自签名证书转成Android支持的BKS格式(需要BouncyCastle工具):

keytool -importcert -file your-cert.crt -keystore your-cert.bks -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider

把生成的your-cert.bks放到res/raw目录下,然后自定义TrustManager让OkHttp(或HttpURLConnection)信任这个证书:

private SSLSocketFactory getCustomSSLSocketFactory(Context context) throws Exception {
    // 加载内置证书
    CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
    InputStream certStream = context.getResources().openRawResource(R.raw.your_cert);
    Certificate cert = certFactory.generateCertificate(certStream);
    certStream.close();

    // 初始化密钥库并添加证书
    KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
    keyStore.load(null, null);
    keyStore.setCertificateEntry("trusted-cert", cert);

    // 创建信任管理器
    TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    tmf.init(keyStore);

    // 构建SSL上下文
    SSLContext sslContext = SSLContext.getInstance("TLS");
    sslContext.init(null, tmf.getTrustManagers(), null);
    return sslContext.getSocketFactory();
}

然后给OkHttpClient配置这个自定义的SSLSocketFactory:

OkHttpClient client = new OkHttpClient.Builder()
        .sslSocketFactory(getCustomSSLSocketFactory(context), (X509TrustManager) tmf.getTrustManagers()[0])
        .build();

方案2:让用户手动安装证书(适合非企业应用)

如果不想内置证书,可以指导用户把自签名证书安装到Android系统的「加密与凭据」里(路径大概是:设置 → 安全 → 加密与凭据 → 从存储安装),这样原生HTTPS调用会自动信任该证书。

二、WebView/Cordova HTML5部分的AJAX调用配置

这部分的处理分Cordova插件和原生WebView两种场景:

Cordova环境

推荐:使用cordova-plugin-advanced-http插件

这个插件专门支持自定义证书信任,把自签名证书放到www/assets目录下,然后初始化时指定证书:

const http = cordova.plugin.http;
// 设置证书锁定模式
http.setServerTrustMode('pinned', () => {
    console.log('信任模式已设置为证书锁定');
}, (err) => {
    console.error('设置信任模式失败', err);
});
// 或者直接指定证书文件
http.setSSLCertMode('your-cert.crt', () => {
    console.log('SSL证书模式已配置');
}, (err) => {
    console.error('配置SSL证书模式失败', err);
});

测试环境临时方案(生产禁用)

如果只是测试,可以在config.xml里加配置允许访问目标域名,但生产环境绝对不能这么做:

<access origin="https://your-backend-domain.com" />
<allow-navigation href="https://your-backend-domain.com/*" />

原生WebView(非Cordova)

Android WebView

自定义WebViewClient,重写onReceivedSslError方法,一定要验证证书再允许加载,别直接忽略所有错误(会被中间人攻击):

webView.setWebViewClient(new WebViewClient() {
    @Override
    public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
        // 这里要对比服务器返回的证书和我们内置的证书是否一致
        if (isOurCustomCert(error.getCertificate())) {
            handler.proceed(); // 证书匹配,允许加载
        } else {
            handler.cancel(); // 证书不匹配,拒绝
        }
    }
});
// 别忘了启用JS和DOM存储,不然AJAX会有问题
WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);
webSettings.setDomStorageEnabled(true);

iOS WKWebView

自定义WKNavigationDelegate,在证书挑战时做验证,逻辑和原生URLSession的处理一致:

func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
    // 这里复用之前原生模块的证书验证逻辑,确认是我们的自签名证书再允许连接
    // 验证通过就调用completionHandler(.useCredential, credential),否则取消
}
关键注意事项
  • 安全性第一:绝对不要直接跳过SSL错误验证(比如Android直接handler.proceed()、iOS直接允许所有挑战),这等于给中间人攻击开了后门,必须严格验证证书的有效性。
  • 证书更新:如果自签名证书过期或更换,要么重新打包APP更新内置证书,要么指导用户重新安装系统证书。
  • 环境区分:测试环境可以临时放宽限制,但生产环境必须严格遵循证书验证流程。

内容的提问来源于stack exchange,提问作者hellopeach

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 03:45:42