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

Android应用与Spring Boot REST服务SSL通信问题求助

嘿,这种情况我之前帮好几个开发者排查过,大概率是证书信任不匹配的问题——浏览器自带了完整的根证书库,能自动识别合法证书,但Android应用默认只会信任系统预装的根CA,如果你用的是自签名证书或者私有CA签发的证书,OkHttp肯定会直接拒接连接。下面给你分场景一步步解决:

场景1:你用的是自签名证书(自己用keytool生成的那种)

生产环境推荐做法:把证书打包到应用,让OkHttp信任它

这是最安全合规的方式,步骤如下:

  1. 导出证书文件:从你的服务端keystore里导出证书为CRT格式
    keytool -exportcert -alias your-cert-alias -keystore your-keystore.jks -file server-cert.crt
    
  2. 放入Android项目:把导出的server-cert.crt放到app/src/main/res/raw目录下(没有raw文件夹就新建一个)
  3. 编写证书加载工具类:创建自定义的SSL信任管理器,让OkHttp信任你的证书
    private SSLSocketFactory getTrustedSSLSocketFactory(Context context) throws Exception {
        // 加载本地证书
        CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
        InputStream certStream = context.getResources().openRawResource(R.raw.server_cert);
        Certificate serverCert = certFactory.generateCertificate(certStream);
        certStream.close();
    
        // 创建包含证书的KeyStore
        KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
        keyStore.load(null, null);
        keyStore.setCertificateEntry("server-cert", serverCert);
    
        // 初始化信任管理器
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(keyStore);
    
        // 创建SSLContext并返回SocketFactory
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, tmf.getTrustManagers(), new SecureRandom());
        return sslContext.getSocketFactory();
    }
    
  4. 配置OkHttp客户端:把自定义的SSLSocketFactory绑定到OkHttp
    OkHttpClient client = new OkHttpClient.Builder()
        .sslSocketFactory(getTrustedSSLSocketFactory(context), 
            (X509TrustManager) TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
                .init((KeyStore) null).getTrustManagers()[0])
        .build();
    
  5. 适配Android 10+网络安全规则:在AndroidManifest.xml<application>标签里添加配置
    android:networkSecurityConfig="@xml/network_security_config"
    
    然后在res/xml下创建network_security_config.xml
    <?xml version="1.0" encoding="utf-8"?>
    <network-security-config>
        <domain-config cleartextTrafficPermitted="false">
            <domain includeSubdomains="true">你的服务端域名或IP</domain>
            <trust-anchors>
                <certificates src="@raw/server_cert" />
                <certificates src="system" /> <!-- 保留系统默认信任的根CA -->
            </trust-anchors>
        </domain-config>
    </network-security-config>
    
场景2:用的是私有CA签发的证书

逻辑和自签名类似,只是把导入的证书换成私有CA的根证书,而不是服务端的单个证书。这样所有由该私有CA签发的证书,你的应用都会信任,适合多服务共享CA的场景。

场景3:用的是正规CA签发的证书,但还是报错

这时候大概率是Spring Boot服务的证书链不完整。比如你的证书是由中间CA签发的,服务端只配置了自己的证书,没有把中间CA的证书一起打包,导致Android端无法完成完整的证书链验证。

解决方法:补全服务端的证书链

  1. 拿到中间CA的证书文件(正规CA提供商都会提供)
  2. 把中间CA证书导入到服务端的keystore里:
    keytool -importcert -alias intermediate-ca -keystore your-keystore.jks -file intermediate.crt
    
  3. 重启Spring Boot服务,确保服务端返回完整的证书链(可以用浏览器查看证书详情,确认证书链是否完整)
调试技巧:先看具体错误信息

OkHttp抛出的SSL异常会明确告诉你问题所在:

  • SSLHandshakeException: unable to find valid certification path to requested target:证书不在应用的信任列表里,按场景1/2处理
  • SSLPeerUnverifiedException: Hostname xxx not verified:证书的域名(CN/SAN字段)和你访问的服务端域名不匹配。要么重新生成包含正确域名的证书,要么仅限测试环境临时设置宽松的主机名验证:
    OkHttpClient client = new OkHttpClient.Builder()
        .hostnameVerifier((hostname, session) -> {
            // 仅限测试!生产环境绝对不能这么做
            return hostname.equals("你的服务端域名") || hostname.equals("192.168.1.100");
        })
        .build();
    

重要提醒

绝对不要在生产环境中使用“跳过证书验证”的代码(比如信任所有证书、不验证主机名),这会让你的应用完全暴露在中间人攻击的风险下,彻底失去HTTPS的加密意义。

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

火山引擎 最新活动