为什么需要HTTPS?

HTTP的三大安全问题

1. 明文传输(窃听)

中间人可以看到所有内容:

客户端 → 路由器: POST /login {"username":"admin", "password":"123456"}
              ↑ 窃听者看到密码!

2. 篡改(中间人攻击)

中间人可以修改请求/响应:

客户端 → 中间人 → 服务器
         ↑ 修改转账金额 1000 → 10000

3. 冒充(钓鱼网站)

客户端访问 http://bank.com
中间人伪造假网站,窃取用户密码

HTTPS = HTTP + TLS/SSL

HTTP over TLS/SSL

应用层     HTTP
         ----
安全层     TLS/SSL(加密)
         ----
传输层     TCP

加密基础

对称加密

相同的密钥加密和解密

加密:明文 + 密钥 → 密文
解密:密文 + 密钥 → 明文

示例:
明文:"Hello"
密钥:"secret123"
密文:"Xf9kL2p"

常见算法:AES、DES、3DES

优点:速度快 缺点:密钥如何安全传递?

非对称加密

公钥加密,私钥解密

公钥:公开,任何人都可以获取
私钥:保密,只有服务器持有

加密:明文 + 公钥 → 密文
解密:密文 + 私钥 → 明文

示例:
服务器生成密钥对:公钥A、私钥B
客户端用公钥A加密:密文C
服务器用私钥B解密:得到明文

常见算法:RSA、ECC

优点:密钥传递安全 缺点:速度慢(比对称加密慢100-1000倍)

HTTPS混合加密

结合两者优点

1. 非对称加密传递对称密钥(握手阶段)
2. 对称加密传输数据(数据传输阶段)

优点:安全 + 高效

TLS握手过程

完整流程(TLS 1.2)

客户端                                 服务器
   |                                      |
   | [1] Client Hello                     |
   |   - 支持的TLS版本                    |
   |   - 支持的加密套件列表               |
   |   - 随机数1(ClientRandom)          |
   |------------------------------------->|
   |                                      |
   | [2] Server Hello                     |
   |   - 选择的TLS版本                    |
   |   - 选择的加密套件                   |
   |   - 随机数2(ServerRandom)          |
   |<-------------------------------------|
   |                                      |
   | [3] Certificate                      |
   |   - 服务器证书(包含公钥)           |
   |<-------------------------------------|
   |                                      |
   | [4] Server Key Exchange(可选)      |
   |<-------------------------------------|
   |                                      |
   | [5] Server Hello Done                |
   |<-------------------------------------|
   |                                      |
   | [6] Client Key Exchange              |
   |   - 预主密钥(用服务器公钥加密)     |
   |------------------------------------->|
   |                                      |
   | [7] Change Cipher Spec               |
   |   - 通知:后续使用加密通信           |
   |------------------------------------->|
   |                                      |
   | [8] Finished                         |
   |   - 加密的握手消息摘要               |
   |------------------------------------->|
   |                                      |
   | [9] Change Cipher Spec               |
   |<-------------------------------------|
   |                                      |
   | [10] Finished                        |
   |<-------------------------------------|
   |                                      |
   | [加密的HTTP数据传输]                 |
   |<------------------------------------>|

密钥生成

主密钥(Master Secret)= PRF(预主密钥, "master secret", ClientRandom + ServerRandom)

会话密钥 = PRF(主密钥, "key expansion", ServerRandom + ClientRandom)

生成6个密钥:
- 客户端加密密钥
- 服务器加密密钥
- 客户端MAC密钥
- 服务器MAC密钥
- 客户端IV
- 服务器IV

TLS 1.3简化握手

TLS 1.3只需1-RTT:

客户端 → 服务器: Client Hello + Key Share(预共享密钥)
客户端 ← 服务器: Server Hello + Key Share + Certificate + Finished

立即开始加密传输!

数字证书与CA

证书内容

证书包含:
- 域名(Subject)
- 公钥
- 有效期
- 颁发机构(Issuer)
- 签名(CA用私钥签名)

证书链

根证书(Root CA)
   ↓ 签名
中间证书(Intermediate CA)
   ↓ 签名
服务器证书(End-Entity Certificate)

证书验证过程

1. 浏览器收到服务器证书
2. 验证域名是否匹配
3. 验证证书是否过期
4. 验证证书签名:
   - 用中间CA的公钥验证服务器证书签名
   - 用根CA的公钥验证中间CA证书签名
5. 根CA证书在浏览器预装列表中
6. 验证通过,信任该证书

查看证书

# OpenSSL查看证书
openssl s_client -connect baidu.com:443 -showcerts

# 输出
Certificate chain
 0 s:/CN=baidu.com
   i:/C=BE/O=GlobalSign nv-sa/CN=GlobalSign Organization Validation CA - SHA256 - G2
 1 s:/C=BE/O=GlobalSign nv-sa/CN=GlobalSign Organization Validation CA - SHA256 - G2
   i:/C=BE/O=GlobalSign nv-sa/OU=Root CA/CN=GlobalSign Root CA

Spring Boot HTTPS配置

生成自签名证书

# 生成keystore(JKS格式)
keytool -genkeypair \
  -alias my-app \
  -keyalg RSA \
  -keysize 2048 \
  -keystore keystore.jks \
  -validity 365

# 输入密码和证书信息
Enter keystore password: 123456
What is your first and last name? localhost
What is the name of your organizational unit? IT
What is the name of your organization? MyCompany
...

Spring Boot配置

# application.yml
server:
  port: 8443
  ssl:
    enabled: true
    key-store: classpath:keystore.jks
    key-store-password: 123456
    key-alias: my-app
    key-store-type: JKS

Java代码

// HTTPS监听
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

// 访问
// https://localhost:8443/api/users

微服务HTTPS实战

案例1:RestTemplate HTTPS调用

@Configuration
public class RestTemplateConfig {

    @Bean
    public RestTemplate httpsRestTemplate() throws Exception {
        // 信任所有证书(仅开发环境!生产禁用)
        TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;

        SSLContext sslContext = SSLContexts.custom()
            .loadTrustMaterial(null, acceptingTrustStrategy)
            .build();

        SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(
            sslContext,
            NoopHostnameVerifier.INSTANCE  // 不验证主机名(仅开发环境)
        );

        CloseableHttpClient httpClient = HttpClients.custom()
            .setSSLSocketFactory(csf)
            .build();

        HttpComponentsClientHttpRequestFactory factory =
            new HttpComponentsClientHttpRequestFactory(httpClient);

        return new RestTemplate(factory);
    }
}

// 生产环境:信任特定证书
@Bean
public RestTemplate productionRestTemplate() throws Exception {
    // 加载信任的证书
    KeyStore trustStore = KeyStore.getInstance("JKS");
    try (InputStream trustStoreStream = getClass().getResourceAsStream("/truststore.jks")) {
        trustStore.load(trustStoreStream, "password".toCharArray());
    }

    SSLContext sslContext = SSLContexts.custom()
        .loadTrustMaterial(trustStore, null)
        .build();

    SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext);

    CloseableHttpClient httpClient = HttpClients.custom()
        .setSSLSocketFactory(csf)
        .build();

    HttpComponentsClientHttpRequestFactory factory =
        new HttpComponentsClientHttpRequestFactory(httpClient);

    return new RestTemplate(factory);
}

案例2:Feign HTTPS调用

# application.yml
feign:
  client:
    config:
      user-service:
        url: https://user-service:8443  # HTTPS
// Feign配置(信任所有证书,仅开发)
@Configuration
public class FeignConfig {

    @Bean
    public Client feignClient() throws Exception {
        TrustManager[] trustAllCerts = new TrustManager[]{
            new X509TrustManager() {
                public X509Certificate[] getAcceptedIssuers() {
                    return new X509Certificate[0];
                }
                public void checkClientTrusted(X509Certificate[] certs, String authType) {}
                public void checkServerTrusted(X509Certificate[] certs, String authType) {}
            }
        };

        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, trustAllCerts, new SecureRandom());

        return new Client.Default(
            sslContext.getSocketFactory(),
            (hostname, session) -> true  // 不验证主机名
        );
    }
}

案例3:Let’s Encrypt免费证书

# 安装Certbot
sudo apt-get install certbot

# 生成证书(需要域名解析到服务器)
sudo certbot certonly --standalone -d api.example.com

# 证书位置
/etc/letsencrypt/live/api.example.com/fullchain.pem  # 证书链
/etc/letsencrypt/live/api.example.com/privkey.pem    # 私钥

# 转换为JKS格式
openssl pkcs12 -export \
  -in /etc/letsencrypt/live/api.example.com/fullchain.pem \
  -inkey /etc/letsencrypt/live/api.example.com/privkey.pem \
  -out keystore.p12 \
  -name api.example.com

keytool -importkeystore \
  -srckeystore keystore.p12 -srcstoretype PKCS12 \
  -destkeystore keystore.jks -deststoretype JKS

# 自动续期(Let's Encrypt证书有效期90天)
sudo crontab -e
0 0 1 * * /usr/bin/certbot renew --quiet

案例4:Nginx反向代理HTTPS

# nginx.conf
server {
    listen 443 ssl http2;
    server_name api.example.com;

    # SSL证书
    ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;

    # SSL配置
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;

    # 代理到后端服务(HTTP)
    location / {
        proxy_pass http://localhost:8080;
        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;
    }
}

# HTTP重定向到HTTPS
server {
    listen 80;
    server_name api.example.com;
    return 301 https://$server_name$request_uri;
}

HTTPS性能优化

1. 会话复用

TLS Session Resumption:

首次握手:完整TLS握手(2-RTT)
后续连接:复用会话ID(1-RTT)

配置:
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;

2. OCSP Stapling

在线证书状态协议:

传统方式:客户端向CA查询证书是否吊销(慢)
OCSP Stapling:服务器预先查询并附带在握手中(快)

Nginx配置:
ssl_stapling on;
ssl_stapling_verify on;

3. HTTP/2 + HTTPS

HTTP/2必须使用HTTPS:

listen 443 ssl http2;  # 启用HTTP/2

总结

核心要点

  1. HTTPS = HTTP + TLS/SSL:解决窃听、篡改、冒充三大问题
  2. 混合加密:非对称加密传递密钥,对称加密传输数据
  3. TLS握手:TLS 1.2需要2-RTT,TLS 1.3仅需1-RTT
  4. 证书验证:通过证书链验证服务器身份
  5. 生产实践:Let’s Encrypt免费证书 + Nginx反向代理

下一篇预告

《HTTP/2协议详解:多路复用与头部压缩》