Android应用与Spring Boot REST服务SSL通信问题求助
嘿,这种情况我之前帮好几个开发者排查过,大概率是证书信任不匹配的问题——浏览器自带了完整的根证书库,能自动识别合法证书,但Android应用默认只会信任系统预装的根CA,如果你用的是自签名证书或者私有CA签发的证书,OkHttp肯定会直接拒接连接。下面给你分场景一步步解决:
场景1:你用的是自签名证书(自己用keytool生成的那种)
生产环境推荐做法:把证书打包到应用,让OkHttp信任它
这是最安全合规的方式,步骤如下:
- 导出证书文件:从你的服务端keystore里导出证书为CRT格式
keytool -exportcert -alias your-cert-alias -keystore your-keystore.jks -file server-cert.crt - 放入Android项目:把导出的
server-cert.crt放到app/src/main/res/raw目录下(没有raw文件夹就新建一个) - 编写证书加载工具类:创建自定义的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(); } - 配置OkHttp客户端:把自定义的SSLSocketFactory绑定到OkHttp
OkHttpClient client = new OkHttpClient.Builder() .sslSocketFactory(getTrustedSSLSocketFactory(context), (X509TrustManager) TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) .init((KeyStore) null).getTrustManagers()[0]) .build(); - 适配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端无法完成完整的证书链验证。
解决方法:补全服务端的证书链
- 拿到中间CA的证书文件(正规CA提供商都会提供)
- 把中间CA证书导入到服务端的keystore里:
keytool -importcert -alias intermediate-ca -keystore your-keystore.jks -file intermediate.crt - 重启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




