Spring Boot 从 HTTP 升级 HTTPS:我踩了 3 个大坑,终于解决了!

摘要:本文详细记录了在 Spring Boot 项目中将 HTTP 服务升级为 HTTPS 过程中遇到的各种问题和解决方案,包括 SSL 证书生成、Maven 资源过滤导致的证书损坏、JDK 版本兼容性等常见问题。希望能帮助遇到类似问题的开发者快速定位和解决问题。

关键词:Spring Boot、HTTPS、SSL、PKCS12、Maven、DerInputStream


一、背景介绍

最近在给一个 Spring Boot 项目开启 HTTPS 时,本以为只是加几行配置、生成一个证书就能搞定。

结果实际操作下来,连续踩了几个坑:

  • keystore.p12 无法加载
  • DerInputStream.getLength(): lengthTag=111, too big
  • IntelliJ IDEA 可以运行,Maven 打包后却启动失败
  • 证书文件明明存在,但运行时总报损坏

最后排查了很久,才发现真正的问题居然是:

Maven 资源过滤把 .p12 二进制证书文件给“改坏了”。

这篇文章会把整个排查过程、根因分析以及最终解决方案完整记录下来,希望能帮你少踩坑。

环境信息

  • Spring Boot 版本:3.x
  • JDK 版本:Corretto 21
  • 构建工具:Maven 3.x
  • IDE:IntelliJ IDEA 2024
  • 证书类型:PKCS12 (.p12)

二、基础配置

2.1 application.yml 配置

在 application.yml 中添加 SSL 配置:

server:
  port: 8081
  ssl:
    key-store: classpath:keystore.p12
    key-store-password: 123456
    key-store-type: PKCS12
    key-alias: local-ssl
    enabled: true

配置说明:

  • key-store: 证书文件路径,classpath: 表示从类路径加载
  • key-store-password: 证书密码
  • key-store-type: 证书类型,推荐使用 PKCS12
  • key-alias: 证书别名
  • enabled: 是否启用 SSL

2.2 生成自签名证书

使用 JDK 自带的 keytool 工具生成测试用的自签名证书:

keytool -genkeypair \
  -alias local-ssl \
  -keyalg RSA \
  -keysize 2048 \
  -storetype PKCS12 \
  -keystore keystore.p12 \
  -storepass 123456 \
  -keypass 123456 \
  -validity 3650 \
  -dname "CN=localhost,OU=Development,O=Company,L=City,ST=State,C=CN" \
  -ext SAN=dns:localhost,ip:127.0.0.1

参数说明:

  • -alias: 证书别名,需与配置文件中的 key-alias 一致
  • -keyalg: 密钥算法,RSA 是最常用的
  • -keysize: 密钥长度,2048 位是推荐的最小值
  • -storetype: 密钥库类型,PKCS12 是行业标准格式
  • -validity: 有效期(天),3650 表示 10 年
  • -dname: 证书持有者信息,CN(Common Name)必须为 localhost
  • -ext SAN: Subject Alternative Name,支持多个域名和 IP

验证证书:

keytool -list -keystore keystore.p12 -storetype PKCS12 -storepass 123456

输出示例:
如果能正常输出证书信息,说明证书本身没有问题。

密钥库类型: PKCS12
密钥库提供方: SUN

您的密钥库包含 1 个条目

local-ssl, 2026年5月23日, PrivateKeyEntry,
证书指纹 (SHA-256): 46:2F:7F:73:78:35:A1:6E:6F:AD:27:8F:57:B1:75:8B:38:3E:7A:61:B8:34:E8:4B:F9:2C:B8:76:00:CD:7D:E0

三、遇到的问题及解决方案

第一个坑:DerInputStream.getLength() 报错

问题描述

项目启动直接报错:

Unable to create key store
Could not load store from 'classpath:keystore.p12'

Caused by:
java.io.IOException:
DerInputStream.getLength(): lengthTag=111, too big.
问题分析

这个错误表明 keystore.p12 文件的 DER 编码格式有问题。可能的原因:

  1. 证书文件在传输或保存过程中损坏
  2. 使用了不兼容的 JDK 版本生成证书
  3. Maven 资源过滤破坏了二进制文件
解决方案

步骤1:删除损坏的证书文件

# Windows PowerShell
Remove-Item E:\projects\myapp\service-web\src\main\resources\keystore.p12

步骤2:重新生成证书

cd E:\projects\myapp\service-web\src\main\resources

# 使用项目指定的 JDK 21
C:\Users\84869\.jdks\corretto-21.0.6\bin\keytool.exe -genkeypair ^
  -alias local-ssl ^
  -keyalg RSA ^
  -keysize 2048 ^
  -storetype PKCS12 ^
  -keystore keystore.p12 ^
  -storepass 123456 ^
  -keypass 123456 ^
  -validity 3650 ^
  -dname "CN=localhost" ^
  -ext SAN=dns:localhost,ip:127.0.0.1 ^
  -noprompt

步骤3:验证新生成的证书

C:\Users\84869\.jdks\corretto-21.0.6\bin\keytool.exe -list ^
  -keystore keystore.p12 ^
  -storetype PKCS12 ^
  -storepass 123456

第二个坑:JDK 版本不一致

问题描述

即使重新生成了证书,启动时仍然报同样的错误。经过深入排查,发现是 JDK 版本不一致 导致的问题。

根本原因
  1. 系统环境变量指向的是 Java 8(java -version 显示 1.8.0_281)
  2. IntelliJ IDEA 配置使用的是 JDK 21(Corretto 21.0.6)
  3. 之前用 Java 8 的 keytool 生成的证书与 JDK 21 运行时不兼容

关键知识点:

  • Java 8 和 Java 21 的 PKCS12 实现有差异
  • Java 8 对 PKCS12 的支持有限,直接使用 -storetype PKCS12 生成的证书可能与其他版本不兼容
  • 不同版本的 JDK 使用的加密算法和默认参数可能不同
解决方案

方案一:统一使用 JDK 21(推荐)

  1. 检查 IntelliJ IDEA 配置

    • 打开 File → Project Structure (Ctrl+Alt+Shift+S)
    • 在 Project 标签下:
      • SDK: 选择 corretto-21.0.6
      • Language level: 选择 21
  2. 配置运行配置的 JRE

    • 打开 Run → Edit Configurations
    • 找到启动配置(ServletInitializer)
    • 在 JRE 下拉框中选择 corretto-21.0.6
    • 点击 Apply 和 OK
  3. 设置 Maven 导入器 JDK

    • 打开 File → Settings → Build, Execution, Deployment → Build Tools → Maven → Importing
    • 设置 JDK for importer 为 corretto-21.0.6
  4. 设置编译器 JDK

    • 打开 File → Settings → Build, Execution, Deployment → Compiler → Java Compiler
    • 设置 Project bytecode version 为 21
  5. 使用 JDK 21 的 keytool 重新生成证书

C:\Users\84869\.jdks\corretto-21.0.6\bin\keytool.exe -genkeypair ^
  -alias local-ssl ^
  -keyalg RSA ^
  -keysize 2048 ^
  -storetype PKCS12 ^
  -keystore keystore.p12 ^
  -storepass 123456 ^
  -keypass 123456 ^
  -validity 3650 ^
  -dname "CN=localhost" ^
  -ext SAN=dns:localhost,ip:127.0.0.1

方案二:临时禁用 SSL(仅用于快速测试)

如果急需启动项目进行其他测试,可以临时禁用 SSL:

server:
  ssl:
    enabled: false  # 临时禁用

然后通过 HTTP 访问:http://localhost:8081/service-web


真正的大坑:Maven 把 .p12 文件过滤坏了

问题描述

配置好 JDK 版本并重新生成证书后,通过 IntelliJ IDEA 直接运行仍然报错。但奇怪的是,源代码目录中的证书文件是正常的。

深入排查

检查源文件和编译后的文件:

# 检查源文件
$srcKeystore = "E:\projects\myapp\service-web\src\main\resources\keystore.p12"
Write-Host "Source size: $((Get-Item $srcKeystore).Length) bytes"

# 检查编译后的文件
$targetKeystore = "E:\projects\myapp\service-web\target\classes\keystore.p12"
Write-Host "Target size: $((Get-Item $targetKeystore).Length) bytes"

# 比较文件哈希值
if ((Get-FileHash $srcKeystore).Hash -eq (Get-FileHash $targetKeystore).Hash) {
    Write-Host "Files are IDENTICAL" -ForegroundColor Green
} else {
    Write-Host "Files are DIFFERENT!" -ForegroundColor Red
}

输出结果:

Source size: 2606 bytes
Target size: 4883 bytes
Files are DIFFERENT!

发现问题! Maven 编译后的证书文件大小发生了变化,说明 Maven 的资源处理过程破坏了证书文件。

根本原因

查看 pom.xml 中的资源配置:

<resources>
    <resource>
        <directory>src/main/resources</directory>
        <excludes>
            <exclude>**/config_generate</exclude>
            <exclude>**/metrics.war</exclude>
            <exclude>**/*.sh</exclude>
            <exclude>static/**</exclude>
        </excludes>
        <filtering>true</filtering>  <!-- 启用资源过滤 -->
    </resource>
</resources>

问题分析:

  1. Maven Resources Plugin 默认会对所有资源文件进行文本过滤(filtering=true)
  2. 文本过滤会将 ${variable} 占位符替换为实际值
  3. 对于二进制文件(如 .p12 证书),文本过滤会破坏其字节结构
  4. 虽然配置了 nonFilteredFileExtensions,但老版本的 Maven Resources Plugin(2.6)不支持此配置

Maven 资源过滤的工作原理:

原始 keystore.p12:  30 82 0A CA 02 01 03 ... (2606 bytes) ✅
经过 Maven 处理后:  30 82 13 1B 02 01 03 ... (4883 bytes) ❌ 被破坏!

Maven 尝试将二进制文件当作 UTF-8 文本处理,导致:

  • 文件大小改变
  • 字节内容损坏
  • DER 编码解析失败
解决方案

修改 pom.xml,明确排除证书文件不被过滤:

<resources>
    <!-- 第一组:处理文本文件,启用变量替换,但排除二进制文件 -->
    <resource>
        <directory>src/main/resources</directory>
        <excludes>
           <!-- 排除证书文件,防止被文本过滤破坏 -->
            <exclude>**/*.p12</exclude>
            <exclude>**/*.jks</exclude>
            <exclude>**/*.keystore</exclude>
        </excludes>
        <filtering>true</filtering>
    </resource>
    
    <!-- 第二组:原样复制二进制文件,不做任何处理 -->
    <resource>
        <directory>src/main/resources</directory>
        <includes>
            <!-- 包含证书文件,确保原样复制 -->
            <include>**/*.p12</include>
            <include>**/*.jks</include>
            <include>**/*.keystore</include>
        </includes>
        <filtering>false</filtering>
    </resource>
</resources>

验证修复效果:

# 清理并重新编译
mvn clean resources:resources -pl service-web -DskipTests

# 检查文件一致性
Source size: 2606 bytes
Target size: 2606 bytes
SUCCESS: Files are IDENTICAL!

# 验证证书有效性
keytool -list -keystore target/classes/keystore.p12 -storetype PKCS12 -storepass 123456

输出:

密钥库类型: PKCS12
密钥库提供方: SUN

您的密钥库包含 1 个条目

local-ssl, 2026年5月23日, PrivateKeyEntry,
证书指纹 (SHA-256): 46:2F:7F:73:78:35:A1:6E:6F:AD:27:8F:57:B1:75:8B:38:3E:7A:61:B8:34:E8:4B:F9:2C:B8:76:00:CD:7D:E0

✅ 问题解决!


四、完整的配置方案

4.1 项目结构

service-web/
├── src/
│   └── main/
│       ├── java/
│       │   └── com/example/paas/startup/
│       │       └── ServletInitializer.java
│       └── resources/
│           ├── application.yml          # 主配置文件
│           ├── keystore.p12             # SSL 证书文件
│           └── ...
├── pom.xml                              # Maven 配置
└── target/
    └── classes/
        ├── application.yml              # 编译后的配置
        └── keystore.p12                 # 编译后的证书(应与源文件一致)

4.2 application.yml 完整配置

server:
  port: 8081
  ssl:
    key-store: classpath:keystore.p12
    key-store-password: 123456
    key-store-type: PKCS12
    key-alias: local-ssl
    enabled: true

# 其他配置...

4.3 pom.xml 资源配置

<build>
    <resources>
        <!-- 文本文件:启用变量替换 -->
        <resource>
            <directory>src/main/resources</directory>
            <excludes>
                <exclude>**/*.p12</exclude>
                <exclude>**/*.jks</exclude>
                <exclude>**/*.keystore</exclude>
            </excludes>
            <filtering>true</filtering>
        </resource>
        
        <!-- 二进制文件:原样复制 -->
        <resource>
            <directory>src/main/resources</directory>
            <includes>
                <include>**/*.p12</include>
                <include>**/*.jks</include>
                <include>**/*.keystore</include>
            </includes>
            <filtering>false</filtering>
        </resource>
    </resources>
    
    <plugins>
        <!-- 其他插件配置... -->
    </plugins>
</build>

4.4 证书生成脚本(Windows)

创建 generate-keystore.bat:

@echo off
echo ========================================
echo 生成 SSL 自签名证书
echo ========================================

set JAVA_HOME=C:\Users\84869\.jdks\corretto-21.0.6
set KEYTOOL=%JAVA_HOME%\bin\keytool.exe
set KEYSTORE_PATH=E:\projects\myapp\service-web\src\main\resources\keystore.p12

echo.
echo 正在删除旧证书...
if exist %KEYSTORE_PATH% del %KEYSTORE_PATH%

echo.
echo 正在生成新证书...
%KEYTOOL% -genkeypair ^
  -alias local-ssl ^
  -keyalg RSA ^
  -keysize 2048 ^
  -storetype PKCS12 ^
  -keystore %KEYSTORE_PATH% ^
  -storepass 123456 ^
  -keypass 123456 ^
  -validity 3650 ^
  -dname "CN=localhost,OU=Development,O=Company,L=City,ST=State,C=CN" ^
  -ext SAN=dns:localhost,ip:127.0.0.1 ^
  -noprompt

echo.
echo 验证证书...
%KEYTOOL% -list -keystore %KEYSTORE_PATH% -storetype PKCS12 -storepass 123456

echo.
echo ========================================
echo 证书生成完成!
echo ========================================
pause

4.5 证书生成脚本(Linux/Mac)

创建 generate-keystore.sh:

#!/bin/bash

echo "========================================"
echo "生成 SSL 自签名证书"
echo "========================================"

JAVA_HOME=/usr/lib/jvm/corretto-21
KEYTOOL=$JAVA_HOME/bin/keytool
KEYSTORE_PATH=./src/main/resources/keystore.p12

echo ""
echo "正在删除旧证书..."
rm -f $KEYSTORE_PATH

echo ""
echo "正在生成新证书..."
$KEYTOOL -genkeypair \
  -alias local-ssl \
  -keyalg RSA \
  -keysize 2048 \
  -storetype PKCS12 \
  -keystore $KEYSTORE_PATH \
  -storepass 123456 \
  -keypass 123456 \
  -validity 3650 \
  -dname "CN=localhost,OU=Development,O=Company,L=City,ST=State,C=CN" \
  -ext SAN=dns:localhost,ip:127.0.0.1 \
  -noprompt

echo ""
echo "验证证书..."
$KEYTOOL -list -keystore $KEYSTORE_PATH -storetype PKCS12 -storepass 123456

echo ""
echo "========================================"
echo "证书生成完成!"
echo "========================================"

五、最佳实践建议

5.1 开发环境

  1. 使用自签名证书

    • 方便快速搭建和测试
    • 注意浏览器会显示安全警告
    • 需要手动接受风险继续访问
  2. 证书管理

    • 将 keystore.p12 加入 .gitignore,避免提交到版本控制
    • 提供证书生成脚本,团队成员可自行生成
    • 文档化证书生成步骤和密码
  3. JDK 版本统一

    • 确保开发团队使用相同的 JDK 版本
    • 在项目中明确指定所需的 JDK 版本
    • 使用 .idea/misc.xml 或 pom.xml 固定 JDK 版本

5.2 生产环境

  1. 使用正式 CA 签发的证书

    • 推荐 Let’s Encrypt(免费)
    • 或购买商业证书(DigiCert、GlobalSign 等)
    • 确保证书链完整
  2. 证书安全管理

    • 不要硬编码密码:使用环境变量或配置中心
    • 定期更换证书和密码
    • 使用强密码策略(至少 12 位,包含大小写字母、数字、特殊字符)
  3. 配置优化

server:
  ssl:
    key-store: classpath:keystore.p12
    key-store-password: ${SSL_KEYSTORE_PASSWORD}  # 从环境变量读取
    key-store-type: PKCS12
    key-alias: production-ssl
    enabled: true
    # 启用 TLS 1.2 和 1.3
    protocol: TLSv1.2,TLSv1.3
    # 配置加密套件
    ciphers: >-
      TLS_AES_256_GCM_SHA384,
      TLS_CHACHA20_POLY1305_SHA256,
      TLS_AES_128_GCM_SHA256
  1. HTTP 自动跳转到 HTTPS

如果需要实现 HTTP 自动跳转到 HTTPS,添加配置类:

@Configuration
public class HttpsConfig {
    
    @Bean
    public ServletWebServerFactory servletContainer() {
        TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory() {
            @Override
            protected void postProcessContext(Context context) {
                SecurityConstraint securityConstraint = new SecurityConstraint();
                securityConstraint.setUserConstraint("CONFIDENTIAL");
                SecurityCollection collection = new SecurityCollection();
                collection.addPattern("/*");
                securityConstraint.addCollection(collection);
                context.addConstraint(securityConstraint);
            }
        };
        
        // 添加 HTTP 连接器,自动跳转到 HTTPS
        Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
        connector.setScheme("http");
        connector.setPort(8080);  // HTTP 端口
        connector.setSecure(false);
        connector.setRedirectPort(8081);  // HTTPS 端口
        tomcat.addAdditionalTomcatConnectors(connector);
        
        return tomcat;
    }
}

5.3 常见问题排查清单

遇到问题时的检查顺序:

  1. ✅ 证书文件是否存在且未损坏
  2. ✅ 证书密码是否正确
  3. ✅ 证书别名是否与配置一致
  4. ✅ JDK 版本是否统一
  5. ✅ Maven 是否正确处理了证书文件
  6. ✅ IDE 运行配置是否使用了正确的 JDK
  7. ✅ 端口是否被占用
  8. ✅ 防火墙是否允许该端口

快速诊断脚本:

# 检查证书文件
$keystore = "src\main\resources\keystore.p12"
if (Test-Path $keystore) {
    Write-Host "✓ 证书文件存在" -ForegroundColor Green
    Write-Host "  大小: $((Get-Item $keystore).Length) bytes"
    
    # 尝试验证证书
    try {
        & keytool -list -keystore $keystore -storetype PKCS12 -storepass 123456 | Out-Null
        Write-Host "✓ 证书有效" -ForegroundColor Green
    } catch {
        Write-Host "✗ 证书损坏或密码错误" -ForegroundColor Red
    }
} else {
    Write-Host "✗ 证书文件不存在" -ForegroundColor Red
}

# 检查 JDK 版本
Write-Host "`nJDK 版本:"
& java -version

# 检查端口占用
Write-Host "`n检查端口 8081:"
$port = Get-NetTCPConnection -LocalPort 8081 -ErrorAction SilentlyContinue
if ($port) {
    Write-Host "✗ 端口已被占用" -ForegroundColor Red
} else {
    Write-Host "✓ 端口可用" -ForegroundColor Green
}

六、总结

核心要点回顾

  1. 证书生成

    • 使用与运行时相同版本的 JDK 生成证书
    • 推荐使用 PKCS12 格式(行业标准)
    • 确保 CN 字段为 localhost 或实际域名
  2. Maven 配置

    • 关键点:必须排除二进制文件(.p12、.jks 等)不被文本过滤
    • 使用两组资源配置分别处理文本和二进制文件
    • 老版本的 Maven Resources Plugin 不支持 nonFilteredFileExtensions
  3. JDK 版本一致性

    • 确保系统环境变量、IDE 配置、Maven 配置使用相同的 JDK 版本
    • Java 8 和 Java 11+/21 的 PKCS12 实现有差异
  4. IDE 配置

    • IntelliJ IDEA 的运行配置必须指定正确的 JRE
    • Maven 导入器和编译器也要使用相同的 JDK

问题根源总结

问题根本原因解决方案
DerInputStream 错误证书文件被 Maven 资源过滤破坏在 pom.xml 中排除证书文件不被过滤
证书加载失败JDK 版本不一致统一使用 JDK 21 生成和运行
文件大小变化Maven 文本过滤处理二进制文件设置 filtering=false 原样复制
浏览器安全警告使用自签名证书手动接受或使用正式 CA 证书

经验教训

  1. 二进制文件特殊处理:证书、图片、压缩包等二进制文件不能被 Maven 的文本过滤处理
  2. 版本一致性重要:JDK、Maven、IDE 的版本配置必须保持一致
  3. 深入分析错误:不要只看表面错误,要深入分析根本原因
  4. 文档化配置过程:记录所有配置步骤,方便团队协作和问题回溯

参考资料


附录 证书脚本(Windows)

A. 常用 keytool 命令

# 生成证书
keytool -genkeypair -alias mycert -keyalg RSA -keysize 2048 -storetype PKCS12 -keystore keystore.p12 -validity 3650

# 查看证书详情
keytool -list -keystore keystore.p12 -storetype PKCS12 -v

# 导出证书(公钥)
keytool -exportcert -keystore keystore.p12 -alias mycert -file cert.cer

# 导入证书
keytool -importcert -keystore truststore.p12 -alias mycert -file cert.cer

# 删除证书
keytool -delete -keystore keystore.p12 -alias mycert

# 更改密码
keytool -storepasswd -keystore keystore.p12

B. 证书信息解读

别名: local-ssl                          # 证书的唯一标识
创建日期: 2026年5月23日                   # 证书生成时间
条目类型: PrivateKeyEntry                # 私钥条目(包含私钥和证书链)
证书链长度: 1                            # 自签名证书,只有根证书
所有者: CN=localhost                     # 证书持有者
发布者: CN=localhost                     # 证书颁发者(自签名时相同)
序列号: 7a3b2c1d                         # 证书唯一序列号
有效期开始日期: 2026-05-23               # 生效时间
有效期截止日期: 2036-05-21               # 过期时间
证书指纹:
  SHA1: D8:65:66:69:...                 # SHA1 指纹(已不推荐)
  SHA256: 66:25:85:A6:...               # SHA256 指纹(推荐)
签名算法名称: SHA384withRSA              # 签名算法
主体公共密钥算法: 2048 位 RSA 密钥       # 公钥算法和长度

最后

如果这篇文章帮你解决了问题,欢迎:

  • 点赞 👍
  • 收藏 ⭐
  • 转发 🔁

也欢迎在评论区交流你遇到的 HTTPS 坑。

很多时候:

真正折磨人的,不是 SSL,而是 Maven。😅

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐