项目背景:
项目版本SpringBoot 3.4.2,jdk17,本地开发环境win11,测试环境Linux红帽+docker运行
以上为基础开发环境,另外就是开发处于内网开发限制较多,导致问题解决繁琐,首先就是项目原来使用RestTemplate方法,网上找了好多有用httpclient5的有用httpclient4.5的我都验证不太好使,历时三天将本地开发环境以及测试环境调试通了,可能不是最优解,但幸运的是问题算是解决了,话不用多说上代码
解决方案一:跳过证书认证,亲测好用但根据要求不能用在生产环境上,测试验证接口通了以后只能含泪放弃此方案。
解决方案二:
Pom
使用的是jdk原生自带的HttpClient
<properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <!-- 添加Spring Boot父POM --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.4.2</version> <relativePath/> <!-- lookup parent from repository --> </parent> <!-- 添加Spring Boot常规依赖 --> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> </dependency> <!-- oracle --> <dependency> <groupId>com.hynnet</groupId> <artifactId>oracle-driver-ojdbc_g</artifactId> <version>12.1.0.2</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.34</version> </dependency> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.8.9</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.9.7</version> </dependency> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>33.3.1-jre</version> </dependency> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-core</artifactId> <version>10.1.39</version> </dependency> <!--knife4j api工具--> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId> <version>4.3.0</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>3.0.4</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-jsqlparser</artifactId> <version>3.5.10</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>6.2.5</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-spring</artifactId> <version>3.5.10</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.19</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.19</version> </dependency> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.113.Final</version> </dependency>
增加配置类
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; import org.springframework.http.client.JdkClientHttpRequestFactory; import org.springframework.web.client.RestTemplate; import javax.net.ssl.SSLContext; import java.io.InputStream; import java.net.http.HttpClient; import java.security.KeyStore; import java.time.Duration; @Configuration @EnableConfigurationProperties(SslProperties.class) public class JavaHttpClientSSLConfig { private final SslProperties sslProperties; public JavaHttpClientSSLConfig(SslProperties sslProperties) { this.sslProperties = sslProperties; } @Bean public RestTemplate sslRestTemplate() throws Exception { // 创建自定义的SSL上下文 SSLContext sslContext = createSSLContext(); // 使用Java内置的HttpClient HttpClient httpClient = HttpClient.newBuilder() .sslContext(sslContext) .connectTimeout(Duration.ofSeconds(5)) .build(); // 创建RequestFactory JdkClientHttpRequestFactory factory = new JdkClientHttpRequestFactory(httpClient); factory.setReadTimeout(Duration.ofSeconds(10)); return new RestTemplate(factory); } private SSLContext createSSLContext() throws Exception { if (sslProperties == null) { throw new IllegalStateException("SSL properties not configured properly"); } // 加载密钥库 KeyStore keyStore = KeyStore.getInstance(sslProperties.getTrustStoreType()); ClassPathResource resource = new ClassPathResource(sslProperties.getCertFile()); try (InputStream inputStream = resource.getInputStream()) { keyStore.load(inputStream, sslProperties.getKeyPassword().toCharArray()); } // 创建KeyManagerFactory javax.net.ssl.KeyManagerFactory keyManagerFactory = javax.net.ssl.KeyManagerFactory.getInstance( javax.net.ssl.KeyManagerFactory.getDefaultAlgorithm()); keyManagerFactory.init(keyStore, sslProperties.getKeyPassword().toCharArray()); // 创建SSL上下文 SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(keyManagerFactory.getKeyManagers(), null, null); return sslContext; } }
配置类
import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Component @ConfigurationProperties(prefix = "client.ssl") @Data public class SslProperties { private String certFile; private String keyFile; private String keyPassword; private String caCertFile; private String trustStoreType; // Getters and Setters }
yml文件配置
#####################################证书-配置###################################### client: ssl: # PEM 证书配置 cert-file: certs/dev/app-xxx.p12 key-file: certs/dev/app-xxx.key key-password: XXX@2025 # 私钥密码(如果没有密码则留空) trust-store-type: PKCS12 # 可选:CA 证书(如果服务器使用自签名证书) # ca-cert-file: certs/dev/accc.pem # API 配置 api: base-url: https://app-xxx cti-endpoint: /api/online-xxx/xxc timeout: 30000 max-connections: 20
在Appliction 里注入RestTemplate
import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.http.ResponseEntity; import org.springframework.web.client.RestTemplate; @SpringBootApplication @MapperScan("com.xxx.xxxx.mapper") public class BmwCtiServerAppliction { public static void main(String[] args) { org.springframework.boot.SpringApplication.run(BmwCtiServerAppliction.class, args); } public ResponseEntity<?> respionseEntity(){ return ResponseEntity.ok().build(); } @Bean public RestTemplate restTemplate() { return new RestTemplate(); // 直接注册到容器 } }
这个时候调用接口会提示证书未被信任,调用接口仍旧失败,提示
2025-08-27 17:45:45.019 |-ERROR [http-nio-8080-exec-1] org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/].[dispatcherServlet] [175] -| Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.web.client.ResourceAccessException: I/O error on POST request for "https://app-xxx.cctrsa-int.china.ali.cloud.xxx/api/xxx-online-xxx/cti": PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target] with root cause
sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

此时需要
在服务器上将域名证书下载下来,另存为pem文件
openssl s_client -connect app-icbm.cctrsa-dev.china.ali.cloud.bmw:443 -showcerts </dev/null 2>/dev/null | openssl x509 -outform PEM > server-dev-cert.pem
openssl s_client -connect app-icbm.cctrsa-int.china.ali.cloud.bmw:443 -showcerts </dev/null 2>/dev/null | openssl x509 -outform PEM > server-int-cert.pem
找到你的jdk目录用管理员CMD执行下方指令
keytool -importcert -alias bmw-icbm-dev-cert -keystore D:\YC\java7\jdk-17.0.12\lib\security\cacerts -file C:\certs\server-dev-cert.pem -storepass changeit
keytool -importcert -alias bmw-icbm-int-cert -keystore D:\YC\java7\jdk-17.0.12\lib\security\cacerts -file C:\certs\server-int-cert.pem -storepass changeit
最后本地测试可以调通
如上为本地开发环境调用成功,下面开始调试服务器上的docker应用,其实原理都一样,就是代码不变的情况下在jdk里增加Java信任库,由于docker镜像启动,想要修改jdk的话就得在镜像上面研究,本来最优解就是重新打一个包含这次调用域名的jdk专用镜像,奈何内网开发有诸多限制,尝试了几次只能无奈作罢,新的思路是在docker run的时候引入信任库,这样改动最小,docker run指令如下:
docker run --name xxxServer --restart=always \ --add-host="app-xxx.cctrsa-int.china.ali.cloud.xx:10.86.161.xxx" \ --add-host="app-xxx.cctrsa-dev.china.ali.cloud.xx:10.86.158.xx" \ -v /data/wwwlogs/xxxServer:/data/wwwlogs/xxxServer \ -v /data/app/xxxServer/server-int-cert.pem:/tmp/xx-cert.pem:ro \ -p 8090:8080 \ -e ENVO=int \ -d registry.xxx-xx.com.cn/microservice/xxxserver-uat:0.0.2 \ sh -c "keytool -importcert -noprompt -alias xxx-icbm-dev -keystore \$JAVA_HOME/lib/security/cacerts -file /tmp/xx-cert.pem -storepass changeit && exec java -jar /app/app.jar"
大概意思就是在docker启动时将信任证书加入jdk,想法没错思路也对,不出意外还是出了意外,翻车了,启动之后进入镜像检查证书最开始查不到后来修改docker run再之后能查到但是还是不行,灵机一动想是不是证书没有加到用的jdk里面去,先放一下检查相关指令留作备用:
# 进入镜像 docker exec -it {appname} bash echo "=== Java环境检查 ===" java -version ls -l $(which java) echo "=== keytool检查 ===" which keytool keytool -help 2>&1 | head -5 echo "=== 证书文件检查 ===" ls -la /tmp/xxx-cert.pem file /tmp/xxx-cert.pem 2>/dev/null || echo "file命令不可用,使用替代方法检查证书" head -3 /tmp/bmw-cert.pem echo "=== 尝试读取证书信息 ===" keytool -printcert -file /tmp/bmw-cert.pem echo ""echo "=== cacerts文件检查 ===" echo "=== 检查其他cacerts位置 ===" find / -name "cacerts" 2>/dev/null | head -5 echo "=== 验证导入结果 ===" keytool -list -keystore /usr/local/jdk1.8.0_191/jre/lib/security/cacerts -storepass changeit | grep -i bmw #其实查到这儿的时候大概就是知道原因了,原因就是这个jdk镜像是由1.8jdk后期修改的17,所以镜像里的javaHome有点乱并且镜像里面为1.8的版本和位置,简而言之一句话就是jdk信任证书加错位置了,下面是搜的指令
5. 检查证书文件本身是否有问题
bash
# 在宿主机上检查证书文件
cat /data/wwwlogs/xxxServer/server-int-cert.pem
# 确保证书格式正确(应该包含)
# -----BEGIN CERTIFICATE-----
# ...证书内容...
# -----END CERTIFICATE-----
# 如果证书格式不对,重新获取
openssl s_client -connect app-x.cctrsa-int.china.ali.cloud.x:443 -showcerts </dev/null | openssl x509 -outform PEM > /data/app/xxxServer/server-int-cert.pem
验证步骤
运行后检查:
bash
# 查看容器日志
docker logs xxServer
# 进入容器验证
docker exec -it xxServer bash
# 检查证书是否导入
keytool -list -keystore /usr/local/jdk1.8.0_191/jre/lib/security/cacerts -storepass changeit | grep -i bmw
# 或者检查自定义信任库
keytool -list -keystore /app/security/custom-cacerts -storepass changeit | grep -i bmw
推荐先运行带调试信息的命令,这样可以清楚地看到哪里出了问题。如果证书导入一直失败,建议使用方法B创建自定义信任库。
这个时候还没死心,想着docker run能解决的事情干毛要搞镜像,毕竟时间紧任务重(md天天加班到八九点),以下为突发奇想(胡思乱想)的docker run,希望有个有缘人能用到吧
#第一版 docker run --name xxxServer --restart=always \ --add-host="app-x.cctrsa-int.china.ali.cloud.x:10.86.161.x" \ --add-host="app-x.cctrsa-dev.china.ali.cloud.x:10.86.158.x" \ -v /data/wwwlogs/xxxServer:/data/wwwlogs/xxxServer \ -v /data/app/xxxServer/server-dev-cert.pem:/tmp/xxx-cert.pem:ro \ -p 8090:8080 \ -e ENVO=test \ -d registry.x-assistance.com.cn/microservice/xxxserver-uat:0.0.3 \ sh -c "keytool -importcert -noprompt -alias bmw-icbm-dev -keystore /date/jdk-17.0.13/lib/security/cacerts -file /tmp/bmw-cert.pem -storepass changeit && exec java -jar /app/app.jar" #第二版 docker run --name xServer --restart=always \ --add-host="app-x.cctrsa-int.china.ali.cloud.x:10.86.161.1x" \ --add-host="app-x.cctrsa-dev.china.ali.cloud.x:10.86.158.1x" \ -v /data/wwwlogs/xServer:/data/wwwlogs/xServer \ -v /data/wwwlogs/xServer/server-int-cert.pem:/tmp/x-cert.pem:ro \ -p 8090:8080 \ -e ENVO=int \ -d registry.x-assistance.com.cn/microservice/xserver-uat:0.0.3 \ sh -c " # 查找Java home和cacerts文件 JAVA_HOME=\$(dirname \$(dirname \$(readlink -f \$(which java || command -v java)))) echo \"Java Home: \$JAVA_HOME\" # 尝试多个可能的cacerts路径 CACERTS_PATH=\"\" for path in \"\$JAVA_HOME/lib/security/cacerts\" \"\$JAVA_HOME/jre/lib/security/cacerts\" \"/etc/ssl/certs/java/cacerts\"; do if [ -f \"\$path\" ]; then CACERTS_PATH=\"\$path\" break fi done if [ -z \"\$CACERTS_PATH\" ]; then echo \"Creating custom truststore...\" mkdir -p /app/security CACERTS_PATH=\"/app/security/custom-cacerts\" # 创建新的信任库 keytool -genkeypair -keyalg RSA -alias temp -keystore \"\$CACERTS_PATH\" -storepass changeit -keypass changeit -dname \"CN=Temp\" -validity 1 keytool -delete -alias temp -keystore \"\$CACERTS_PATH\" -storepass changeit fi echo \"Using truststore: \$CACERTS_PATH\" # 导入证书 keytool -importcert -noprompt -alias bmw-icbm-int -keystore \"\$CACERTS_PATH\" -file /tmp/bmw-cert.pem -storepass changeit # 验证证书导入 echo \"Checking imported certificates:\" keytool -list -keystore \"\$CACERTS_PATH\" -storepass changeit | grep bmw-icbm-int # 设置Java选项使用正确的信任库 export JAVA_OPTS=\"\$JAVA_OPTS -Djavax.net.ssl.trustStore=\$CACERTS_PATH -Djavax.net.ssl.trustStorePassword=changeit\" echo \"Starting application with JAVA_OPTS: \$JAVA_OPTS\" exec java \$JAVA_OPTS -jar /app/app.jar " #第三版 docker run --name xServer --restart=always \ --add-host="app-x.cctrsa-int.china.ali.cloud.x:10.86.161.1x" \ --add-host="app-x.cctrsa-dev.china.ali.cloud.bmw:10.86.158.1x" \ -v /data/wwwlogs/xServer:/data/wwwlogs/xServer \ -v /data/app/xServer/server-int-cert.pem:/tmp/x-cert.pem:ro \ -p 8090:8080 \ -e ENVO=int \ -d registry.x-assistance.com.cn/microservice/xserver-uat:0.0.3 \ sh -c " echo '=== 开始导入证书 ===' # 导入到所有找到的cacerts文件 for cacerts in \ /usr/local/jdk1.8.0_191/jre/lib/security/cacerts \ /date/jdk-17.0.13/lib/security/cacerts \ /etc/pki/java/cacerts \ /etc/pki/ca-trust/extracted/java/cacerts; do if [ -f \"\$cacerts\" ]; then echo \"导入证书到: \$cacerts\" keytool -importcert -noprompt -alias x-icbm-int -keystore \"\$cacerts\" -file /tmp/x-cert.pem -storepass changeit # 验证导入 keytool -list -keystore \"\$cacerts\" -storepass changeit | grep -i bmw && echo \"导入成功\" || echo \"导入可能失败\" echo '' fi done echo '=== 证书导入完成 ===' echo '检查当前Java版本:' java -version echo '' # 确定主Java路径并设置正确的信任库 JAVA_HOME=\$(dirname \$(dirname \$(readlink -f \$(which java)))) echo \"检测到JAVA_HOME: \$JAVA_HOME\" MAIN_CACERTS=\"\$JAVA_HOME/jre/lib/security/cacerts\" if [ -f \"\$MAIN_CACERTS\" ]; then export JAVA_OPTS=\"\$JAVA_OPTS -Djavax.net.ssl.trustStore=\$MAIN_CACERTS -Djavax.net.ssl.trustStorePassword=changeit\" echo \"设置主信任库: \$MAIN_CACERTS\" fi echo '启动应用...' exec java \$JAVA_OPTS -jar /app/app.jar " #第四版 docker run --name xServer --restart=always \ --add-host="app-x.cctrsa-int.china.ali.cloud.x:10.86.161.x" \ --add-host="app-x.cctrsa-dev.china.ali.cloud.x:10.86.158.1x" \ -v /data/wwwlogs/xServer:/data/wwwlogs/xServer \ -v /data/app/xServer/server-int-cert.pem:/tmp/x-cert.pem \ -p 8090:8080 \ -e ENVO=int \ -d registry.x-assistance.com.cn/microservice/xserver-uat:0.0.3 \ sh -c " set -x # 开启详细调试输出 echo '=== 调试信息开始 ===' # 设置Java环境 export JAVA_HOME=/usr/local/jdk1.8.0_191 export PATH=\$JAVA_HOME/bin:\$PATH echo 'Java版本:' java -version echo '' echo 'keytool位置:' which keytool echo '' echo '证书文件信息:' ls -la /tmp/bmw-cert.pem echo '' echo '证书文件内容开头:' head -3 /tmp/bmw-cert.pem echo '' echo '证书文件内容结尾:' tail -3 /tmp/bmw-cert.pem echo '' # 先尝试打印证书信息 echo '尝试读取证书信息:' keytool -printcert -file /tmp/bmw-cert.pem || echo '打印证书失败' echo '' # 导入证书 CACERTS=\$JAVA_HOME/jre/lib/security/cacerts echo '导入证书到: '\$CACERTS # 检查cacerts文件是否存在 if [ -f \"\$CACERTS\" ]; then echo 'cacerts文件存在,开始导入...' keytool -importcert -v -noprompt -alias bmw-icbm-int -keystore \"\$CACERTS\" -file /tmp/bmw-cert.pem -storepass changeit echo '导入完成,退出代码: '\$? # 验证导入 echo '验证导入结果:' keytool -list -keystore \"\$CACERTS\" -storepass changeit | grep -i bmw echo '验证完成' else echo '错误: cacerts文件不存在于 '\$CACERTS echo '找到的cacerts文件:' find / -name cacerts 2>/dev/null fi echo '=== 调试信息结束 ===' # 启动应用 export JAVA_OPTS=\"-Djavax.net.ssl.trustStore=\$CACERTS -Djavax.net.ssl.trustStorePassword=changeit\" exec java \$JAVA_OPTS -jar /app/app.jar "
以上个人觉得没问题然后就是和我实际情况不一样(如果jdk镜像没问题可以尝试这个方法)
故此,开始思索打jdk镜像的主意但是网络不支持,启动的时候又因为基础镜像不行,询问了一下同事我俩一致觉得可以在打服务镜像的时候修正jdkJavaHome。于是乎,能解决问题的方法信息出炉了
就是先在项目中放Dockerfile的位置把证书放好一会导入的时候使用项目结构大致为

然后修改Dockerfile:
FROM registry.xxx-assistance.com.cn/java/jdk:17.0.13 WORKDIR / # 设置正确的JDK路径(根据实际查找结果) ENV JAVA_HOME=/date/jdk-17.0.13 ENV PATH=$JAVA_HOME/bin:$PATH # 验证JDK版本(构建时确认正确性) RUN echo "使用的JDK路径: $JAVA_HOME" && \ java -version && \ javac -version # 添加证书文件到临时目录 ADD certs/server-int-cert.pem /tmp/server-int-cert.pem ADD certs/server-dev-cert.pem /tmp/server-dev-cert.pem # 导入证书到正确的Java信任库并清理 RUN keytool -importcert -trustcacerts -file /tmp/server-int-cert.pem \ -keystore $JAVA_HOME/lib/security/cacerts -storepass changeit -noprompt -alias server-int-cert && \ keytool -importcert -trustcacerts -file /tmp/server-dev-cert.pem \ -keystore $JAVA_HOME/lib/security/cacerts -storepass changeit -noprompt -alias server-dev-cert && \ rm -f /tmp/server-int-cert.pem /tmp/server-dev-cert.pem # 添加应用程序JAR包 ADD xxxServer-0.0.1-SNAPSHOT.jar /xxxServer-0.0.1-SNAPSHOT.jar # 环境配置 ENV ENVO="dev" ENV TZ=Asia/Shanghai RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo "$TZ" > /etc/timezone ENV LANG en_US.UTF-8 # 启动命令(保留source /etc/profile确保环境变量生效) ENTRYPOINT [ "sh", "-c", "source /etc/profile && java -Djava.security.egd=file:/dev/./urandom -jar /xxxServer-0.0.1-SNAPSHOT.jar --spring.profiles.active=$ENVO" ]
如此,问题解决。。。。。。。。
继续快(ku)乐(bi)的调试接口去了┭┮﹏┭┮
本文来自博客园,作者:綦霖,转载请注明原文链接:https://www.cnblogs.com/yc-weblog/p/19061577
浙公网安备 33010602011771号