你的 API 还在裸奔吗?是时候给 Spring Boot 穿上“防弹衣”了。

你好,我是老张。今天我们不聊虚的,直接上一套能跑在生产环境、能过 SSL Labs A+ 评级的 HTTPS 全家桶方案。

网上讲 HTTPS 的文章一抓一大把,但大部分要么只讲 Nginx 配置,要么只讲 Spring Boot 代码,很少有文章把“从证书申请到 Nginx 安全加固,再到 Spring Boot 正确识别 HTTPS”这条链路完整串起来

这篇文章的目标只有一个:让你照着抄,就能跑通一套生产级的 HTTPS 架构

📌 版本声明:本文基于 Nginx 1.24+Spring Boot 3.xOpenSSL 3.x,acme.sh 版本 v3.0+。

📑 本文导航

text

一、为什么 HTTPS 是“必选项”而非“可选项”
二、HTTP vs HTTPS:一张表看懂差距
三、架构解析:SSL 终止模型与通信链路
四、证书自动化:acme.sh 三步搞定免费证书
五、生产级 Nginx 配置(安全加固 + 性能优化)
六、Spring Boot 后端配置(解决反向代理后的协议感知痛点)
七、进阶:mTLS 双向认证
八、故障排查与自动化运维
九、总结与互动

一、为什么 HTTPS 是“必选项”而非“可选项”

2026 年,如果你的服务还在跑 HTTP,基本等于在大街上裸奔

三大驱动力让你别无选择

  1. 浏览器强制干预:从 Chrome 开始,所有主流浏览器已将 HTTP 页面标记为“不安全”,地址栏直接显示醒目的“⚠️ 不安全”警告。

  2. SEO 排名降权:Google 明确将 HTTPS 作为搜索排名信号,HTTP 站点在同等内容下排名更低。

  3. 合规与数据安全:《数据安全法》《个人信息保护法》均要求传输过程中的个人信息需采取加密措施,HTTP 明文传输在法律层面即构成违规。

💡 一句话总结:HTTPS 不是“锦上添花”,而是现代 Web 服务的准入门槛

二、HTTP vs HTTPS:一张表看懂差距

对比维度 HTTP HTTPS
传输加密 ❌ 明文传输,中间人可完整窃听 ✅ TLS 加密,防窃听、防篡改
数据完整性 ❌ 数据可被中间人篡改 ✅ 加密+签名,篡改即失效
身份认证 ❌ 无法确认服务端身份 ✅ 证书体系验证服务端真实身份
浏览器标识 ⚠️ 显示“不安全” ✅ 显示安全锁(DV)或企业名称(OV/EV)
SEO 影响 ⚠️ 排名权重低于 HTTPS ✅ Google 明确将 HTTPS 作为排名信号
HTTP/2 支持 ❌ 不支持(浏览器强制要求 HTTPS) ✅ 原生支持,性能提升 30%~50%
HSTS 预加载 ❌ 无法提交 ✅ 可提交至 HSTS Preload List
现代 Web API ❌ 无法使用 Geolocation、Service Worker 等 ✅ 全部可用

参考标准:Mozilla 基金会维护的 SSL Configuration Generator 是业界公认的 TLS 配置权威指南。本文配置即基于 Mozilla Intermediate 配置文件——兼容最近 5+ 年所有客户端,同时保持高强度安全标准。

三、架构解析:SSL 终止模型与通信链路

在典型的微服务架构中,我们采用 SSL 终止(SSL Termination) 模型:

text

[ 浏览器 ] 
    │
    ▼ HTTPS (TLS 1.2/1.3, 端口 443)
    │
[ Nginx 反向代理 ]  ← TLS 在此处解密
    │
    ▼ HTTP (明文, 端口 8080)  内网传输
    │
[ Spring Boot 应用 ]

为什么不让 Spring Boot 直接暴露 HTTPS?

考量 Nginx 终止 SSL Spring Boot 原生 HTTPS
性能 Nginx 的 TLS 处理性能远超 Tomcat Tomcat 处理 TLS 有额外开销
证书管理 统一管理,证书更新只需 reload Nginx 每个实例都要管理证书
负载均衡 Nginx 天然支持负载均衡 需要额外方案
运维复杂度 低(一个入口统一配置) 高(每个实例独立配置)

核心通信模型

  1. 客户端与 Nginx 之间:HTTPS(TLS 加密)

  2. Nginx 与 Spring Boot 之间:HTTP(内网明文,性能最优)

  3. Nginx 通过 X-Forwarded-* 头部将原始请求信息传递给后端

四、证书生态与自动化实战

4.1 免费证书 vs 商业证书
对比维度 Let's Encrypt(免费) 商业证书(DigiCert/GlobalSign 等)
价格 免费 每年数百至数万美元
有效期 90 天 1-2 年
验证级别 DV(仅验证域名) OV(企业验证)/ EV(扩展验证)
适用场景 个人站点、中小型企业、测试环境 金融机构、电商、政府网站
自动化 ✅ 原生支持 ACME 协议 ⚠️ 部分支持,通常需要手动
信任度 浏览器信任,但地址栏仅显示安全锁 OV/EV 证书显示企业名称,信任度更高

💡 建议:90% 的互联网业务用 Let's Encrypt 完全够用。本文采用 acme.sh ——纯 Shell 实现、零依赖、内置数十家 DNS API 支持,是目前最流行的 ACME 客户端。

4.2 acme.sh 安装与证书申请(三步法)

环境准备

bash

# 确保 curl 和 cron 已安装
which curl || apt-get install -y curl
which crontab || apt-get install -y cron
systemctl enable cron && systemctl start cron

第一步:安装 acme.sh

bash

# 安装 acme.sh(自动注册 cron 定时任务)
curl https://get.acme.sh | sh -s email=your-email@example.com
source ~/.bashrc

# 验证安装
acme.sh --version

⚠️ 注意:acme.sh 默认 CA 是 ZeroSSL,免费额度有限。建议显式切换为 Let's Encrypt:

bash

acme.sh --set-default-ca --server letsencrypt

第二步:申请证书(以阿里云 DNS 为例)

对于需要通配符证书(*.example.com)或无法开放 80 端口的场景,推荐 DNS-01 验证方式

bash

# 设置阿里云 DNS API 密钥(推荐用配置文件方式,更安全)
export Ali_Key="你的 AccessKey ID"
export Ali_Secret="你的 AccessKey Secret"

# 申请泛域名证书
acme.sh --issue --dns dns_ali -d example.com -d '*.example.com'

支持的 DNS 服务商:Cloudflare、阿里云、DNSPod、AWS Route53 等数十家。

第三步:安装证书到 Nginx

bash

acme.sh --install-cert -d example.com \
  --key-file /etc/nginx/ssl/example.com.key \
  --fullchain-file /etc/nginx/ssl/example.com.crt \
  --reloadcmd "service nginx force-reload"

⚠️ 关键--reloadcmd 中的 force-reload 会强制重新加载证书,普通的 reload 可能不会重新读取证书文件。

五、生产级 Nginx 配置(安全加固 + 性能优化)

5.1 核心安全指令速查表
指令 推荐值 作用
ssl_protocols TLSv1.2 TLSv1.3 禁用 SSLv2/SSLv3/TLSv1.0/TLSv1.1
ssl_ciphers 见下方配置 仅启用 AEAD + ECDHE 强套件
ssl_prefer_server_ciphers off(TLS 1.3 不适用) 让客户端选择最优套件
ssl_session_cache shared:SSL:10m 会话缓存,提升握手性能约 30%
ssl_session_timeout 1d 会话超时时间
ssl_session_tickets off 禁用 Session Ticket(更安全)
ssl_stapling on 启用 OCSP Stapling,提升 TLS 握手速度
add_header Strict-Transport-Security max-age=63072000 强制浏览器 2 年内仅使用 HTTPS
5.2 完整 Nginx Server Block 配置

nginx

# /etc/nginx/sites-available/example.com
# 基于 Mozilla Intermediate 配置文件[reference:17]

server {
    # ---------- 监听配置 ----------
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name example.com;

    # ---------- 证书路径 ----------
    ssl_certificate /etc/nginx/ssl/example.com.crt;      # 完整证书链
    ssl_certificate_key /etc/nginx/ssl/example.com.key;  # 私钥(权限 600)

    # ---------- TLS 协议版本 ----------
    # 仅启用 TLS 1.2 和 1.3,禁用所有老旧协议
    ssl_protocols TLSv1.2 TLSv1.3;

    # ---------- 加密套件 ----------
    # TLS 1.3 套件前置,全部为 AEAD + ECDHE(支持前向保密 PFS)
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
    
    # TLS 1.3 下此指令无效;TLS 1.2 下让客户端选择最优套件
    ssl_prefer_server_ciphers off;

    # ---------- 会话缓存(性能优化) ----------
    ssl_session_cache shared:SSL:10m;    # 10MB 共享缓存,约可存储 40000 个会话
    ssl_session_timeout 1d;              # 会话有效期 1 天
    ssl_session_tickets off;             # 禁用 Session Ticket

    # ---------- OCSP Stapling(性能优化) ----------
    ssl_stapling on;
    ssl_stapling_verify on;
    resolver 8.8.8.8 8.8.4.4 valid=300s;
    resolver_timeout 5s;

    # ---------- HSTS(强制 HTTPS) ----------
    # max-age=63072000(2年),includeSubDomains 覆盖所有子域名
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

    # ---------- 其他安全响应头 ----------
    add_header X-Frame-Options "SAMEORIGIN" always;           # 防点击劫持
    add_header X-Content-Type-Options "nosniff" always;       # 防 MIME 类型嗅探
    add_header Content-Security-Policy "default-src 'self'" always;  # CSP 基础策略

    # ---------- 反向代理到 Spring Boot ----------
    location / {
        proxy_pass http://127.0.0.1:8080;
        
        # 转发原始请求信息(关键!后端需要这些头识别 HTTPS)
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;   # 告知后端原始协议
        proxy_set_header X-Forwarded-Host $host;      # 告知后端原始域名
        proxy_set_header X-Forwarded-Port $server_port;

        # HTTP 连接优化
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        
        # 超时配置
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }
}

# ---------- HTTP 强制跳转 HTTPS ----------
server {
    listen 80;
    listen [::]:80;
    server_name example.com;
    # 301 永久重定向到 HTTPS
    return 301 https://$server_name$request_uri;
}

⚠️ 重要提醒

  • 私钥文件权限必须为 600chmod 600 /etc/nginx/ssl/example.com.key

  • 配置完成后执行 nginx -t 检查语法,再 systemctl reload nginx

六、前后端打通:Spring Boot 如何感知真实协议?

6.1 错误做法(典型痛点)

很多开发者在 Nginx 配置好 HTTPS 后,发现 Spring Boot 生成的重定向 URL 仍然是 http://,导致 混合内容(Mixed Content) 警告——页面通过 HTTPS 加载,但其中的资源链接却是 HTTP,浏览器会直接阻止加载。

根本原因:Nginx 转发给 Spring Boot 的是 HTTP 请求(proxy_pass http://127.0.0.1:8080),Spring Boot 默认认为所有请求都是 HTTP。

6.2 正确配置(两步解决)

第一步:Nginx 已配置转发头部(上一步已完成):

nginx

proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;

第二步:Spring Boot 配置(application.yml) :

yaml

# src/main/resources/application.yml
server:
  port: 8080
  # 关键配置:启用转发头部处理
  # FRAMEWORK:由 Spring Boot 自身处理(推荐 Spring Boot 3.x)
  # NATIVE:由底层 Servlet 容器(Tomcat)处理
  forward-headers-strategy: FRAMEWORK

或使用 application.properties

properties

server.port=8080
server.forward-headers-strategy=FRAMEWORK

📌 原理:当 server.forward-headers-strategy=FRAMEWORK 时,Spring Boot 会自动检测 X-Forwarded-Proto 头部。如果值为 https,则 HttpServletRequest.isSecure() 返回 truerequest.getScheme() 返回 https

6.3 验证代码(Java Controller)

java

// src/main/java/com/example/demo/controller/SslController.java
package com.example.demo.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

@RestController
public class SslController {

    @GetMapping("/api/ssl-info")
    public Map<String, Object> getSslInfo(HttpServletRequest request) {
        Map<String, Object> info = new HashMap<>();
        
        // 这些值在正确配置后将反映真实的 HTTPS 信息
        info.put("scheme", request.getScheme());              // 应为 "https"
        info.put("isSecure", request.isSecure());             // 应为 true
        info.put("serverName", request.getServerName());      // 原始域名
        info.put("serverPort", request.getServerPort());      // 443
        info.put("remoteAddr", request.getRemoteAddr());      // 真实客户端 IP
        info.put("protocolHeader", request.getHeader("X-Forwarded-Proto"));
        
        return info;
    }
}

验证请求与响应

bash

# 发起请求
curl -k https://example.com/api/ssl-info

# 预期响应
{
  "scheme": "https",
  "isSecure": true,
  "serverName": "example.com",
  "serverPort": 443,
  "remoteAddr": "192.168.1.100",
  "protocolHeader": "https"
}
6.4 Spring Security 强制 HTTPS

如果你的应用使用 Spring Security,可以强制所有请求必须通过 HTTPS 访问:

java

// src/main/java/com/example/demo/config/SecurityConfig.java
package com.example.demo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .requiresChannel(channel -> channel
                .anyRequest().requiresSecure()  // 所有请求必须 HTTPS
            )
            .authorizeHttpRequests(auth -> auth
                .anyRequest().permitAll()
            );
        return http.build();
    }
}

七、进阶安全场景:mTLS 双向认证

mTLS(Mutual TLS)不仅服务端向客户端出示证书,客户端也需向服务端出示证书,适用于微服务间认证IoT 设备接入等场景。

7.1 Nginx 开启客户端证书验证

nginx

server {
    listen 443 ssl http2;
    server_name api.internal.example.com;

    # 服务端证书(同上)
    ssl_certificate /etc/nginx/ssl/server.crt;
    ssl_certificate_key /etc/nginx/ssl/server.key;

    # ---------- 客户端证书验证(mTLS) ----------
    # CA 证书:用于验证客户端证书的签名
    ssl_client_certificate /etc/nginx/ssl/ca.crt;
    ssl_verify_client on;                    # 强制验证客户端证书
    ssl_verify_depth 2;                      # 证书链验证深度

    location / {
        proxy_pass http://127.0.0.1:8080;
        
        # 将客户端证书信息传递给后端
        proxy_set_header X-Client-Cert $ssl_client_cert;
        proxy_set_header X-Client-Verify $ssl_client_verify;
        proxy_set_header X-Client-Subject $ssl_client_s_dn;
        
        # 其他 proxy_set_header 同前...
    }
}
7.2 Spring Boot 解析客户端证书

java

// src/main/java/com/example/demo/controller/MtlsController.java
package com.example.demo.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;

@RestController
public class MtlsController {

    @GetMapping("/api/client-info")
    public Map<String, Object> getClientInfo(HttpServletRequest request) {
        Map<String, Object> info = new HashMap<>();
        
        // 从请求头获取 Nginx 传递的证书信息
        info.put("clientCert", request.getHeader("X-Client-Cert"));
        info.put("clientVerify", request.getHeader("X-Client-Verify"));
        info.put("clientSubject", request.getHeader("X-Client-Subject"));
        
        // 或者直接从 Tomcat 的 attribute 中获取证书对象
        X509Certificate[] certs = (X509Certificate[]) request
            .getAttribute("javax.servlet.request.X509Certificate");
        if (certs != null && certs.length > 0) {
            info.put("certSubjectDN", certs[0].getSubjectDN().toString());
            info.put("certIssuerDN", certs[0].getIssuerDN().toString());
            info.put("certSerialNumber", certs[0].getSerialNumber().toString());
        }
        
        return info;
    }
}

八、故障排查与自动化运维

8.1 三大常见报错及解决方案
报错信息 原因 解决方案
SSL_ERROR_BAD_CERT_DOMAIN 证书域名与访问域名不匹配 检查证书是否包含正确域名(SAN 字段)
NET::ERR_CERT_DATE_INVALID 证书过期 执行 acme.sh --renew -d example.com 续期
ERR_SSL_PROTOCOL_ERROR TLS 版本或密码套件不兼容 检查 ssl_protocols 是否包含 TLSv1.2/TLSv1.3
8.2 证书无人值守轮换(cron 定时任务)

acme.sh 安装时会自动添加 cron 任务,每天检查证书有效期,到期前自动续期。

验证定时任务

bash

crontab -l
# 应看到类似:
# 0 0 * * * /root/.acme.sh/acme.sh --cron --home /root/.acme.sh > /dev/null

手动触发续期测试

bash

acme.sh --cron --home /root/.acme.sh

systemd timer 方案(可选,更现代化):

bash

# /etc/systemd/system/acme-renew.service
[Service]
Type=oneshot
ExecStart=/root/.acme.sh/acme.sh --cron --home /root/.acme.sh
ExecStartPost=/usr/sbin/nginx -t && /usr/sbin/nginx -s reload

# /etc/systemd/system/acme-renew.timer
[Timer]
OnCalendar=daily
Persistent=true

[Install]
WantedBy=timers.target

bash

systemctl enable acme-renew.timer
systemctl start acme-renew.timer

九、总结与技术升华

我们从头到尾走完了一条完整的 HTTPS 落地链路:

  1. 证书申请:acme.sh + DNS API → 自动化获取 Let's Encrypt 证书

  2. Nginx 安全加固:TLS 1.2/1.3 + 强密码套件 + HSTS + OCSP Stapling

  3. 反向代理:SSL 终止模型 + 转发 X-Forwarded-* 头部

  4. Spring Boot 适配forward-headers-strategy: FRAMEWORK → 正确感知 HTTPS

  5. 进阶 mTLS:双向认证 + 证书信息传递

安全是无声的承诺。 用户访问你的网站时看不到 TLS 握手、看不到证书验证、看不到加密传输——但他们能感受到“这个网站是可信的”。

这套配置能让你的网站在 SSL Labs 测试中获得 A+ 评级,同时保持优秀的性能表现。

十、粉丝福利与互动

免责声明:本文配置及脚本均基于公开技术文档及个人生产环境经验总结,仅供参考。使用前请务必在测试环境验证,因配置不当导致的任何损失,本文作者不承担责任。部分工具(如 acme.sh、Nginx)版权归各自所有者。

💬 互动话题

你在生产环境中踩过哪些 HTTPS 的坑?

是证书过期导致业务中断?还是反向代理后重定向 URL 一直是 HTTP?又或者是 mTLS 配置时证书链验证失败?

欢迎在评论区分享你的经历,每一条我都会认真回复。点赞、收藏、转发三连,让更多朋友告别“裸奔”!

🎁 全套 Spring Boot + Nginx HTTPS 生产级资料包

我帮你整理了一套 “从零到 A+ 评级”的 HTTPS 全家桶资料,评论区回复 “HTTPS” 即可免费领取:

① Nginx + Spring Boot 生产级配置文件模板(含本文完整配置,开箱即用)
② acme.sh 一键申请 + 自动续期脚本(支持阿里云/腾讯云/DNSPod/Cloudflare)
③ SSL Labs A+ 评级 Nginx 配置速查表(PDF 打印版)
④ Spring Boot 3.x 转发头部正确配置 YAML/Properties 示例
⑤ mTLS 双向认证完整代码示例(Nginx 配置 + Java Controller 解析)
⑥ 生产环境 HTTPS 故障排查命令速查手册
⑦ HSTS Preload List 提交指南与域名安全评分自检表
⑧ Let's Encrypt vs 商业证书选型决策树
⑨ 2026 年 Mozilla SSL 配置指南中文翻译版(含 Intermediate/Modern 配置)

👇 领取方式
Logo

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

更多推荐