Skip to content

TLS 和 SSL 配置

TIP

本文将深入讲解 Spring Cloud Gateway 中 TLS 和 SSL 的配置方法,包括 HTTPS 监听、证书信任管理以及 TLS 握手超时配置等核心内容。

概述

在微服务架构中,安全通信是至关重要的。Spring Cloud Gateway 作为微服务的入口网关,需要支持 HTTPS 协议来确保客户端与网关之间的通信安全。同时,网关与后端服务之间的通信也可能需要 TLS/SSL 加密。

Spring Cloud Gateway 提供了完整的 TLS/SSL 配置支持,包括:

  • 网关自身的 HTTPS 监听配置
  • 与后端 HTTPS 服务的信任管理
  • TLS 握手超时配置

网关 HTTPS 监听配置

基本 SSL 配置

要让 Spring Cloud Gateway 监听 HTTPS 请求,需要按照标准的 Spring Boot 服务器配置方式进行设置。

yaml
server:
  ssl:
    enabled: true # 启用 SSL
    key-alias: scg # 密钥别名
    key-store-password: scg1234 # 密钥库密码
    key-store: classpath:scg-keystore.p12 # 密钥库文件位置
    key-store-type: PKCS12 # 密钥库类型
kotlin
@Configuration
@ConfigurationProperties(prefix = "server.ssl")
class SslConfiguration {

    /**
     * 是否启用 SSL
     */
    var enabled: Boolean = false

    /**
     * 密钥别名
     */
    var keyAlias: String? = null

    /**
     * 密钥库密码
     */
    var keyStorePassword: String? = null

    /**
     * 密钥库文件路径
     */
    var keyStore: String? = null

    /**
     * 密钥库类型,常用 PKCS12 格式
     */
    var keyStoreType: String = "PKCS12"
}

在生产环境中,密钥库密码等敏感信息应该通过环境变量或安全的配置管理工具进行管理,避免直接写在配置文件中。

密钥库准备

在实际业务场景中,你需要准备一个有效的证书:

bash
# 生成自签名证书(仅用于开发测试)
keytool -genkeypair -alias scg -keyalg RSA -keysize 2048 \
  -storetype PKCS12 -keystore scg-keystore.p12 \
  -validity 365 -storepass scg1234

后端服务 SSL 信任配置

开发环境:信任所有证书

在开发或测试环境中,可以配置网关信任所有下游服务的证书:

yaml
spring:
  cloud:
    gateway:
      httpclient:
        ssl:
          useInsecureTrustManager: true # 信任所有证书(仅用于开发)

WARNING

useInsecureTrustManager: true 配置会信任所有证书,包括自签名和无效证书。这在生产环境中是极其危险的,只能用于开发和测试环境。

生产环境:指定信任证书

在生产环境中,应该明确指定信任的证书:

yaml
spring:
  cloud:
    gateway:
      httpclient:
        ssl:
          trustedX509Certificates:
            - cert1.pem # 信任的证书文件 1
            - cert2.pem # 信任的证书文件 2

系统默认信任库

如果没有配置特定的信任证书,Spring Cloud Gateway 会使用系统默认的信任库。你也可以通过 JVM 系统属性来指定:

bash
# 启动时指定信任库
java -Djavax.net.ssl.trustStore=/path/to/truststore.jks \
     -Djavax.net.ssl.trustStorePassword=password \
     -jar gateway-application.jar

TLS 握手配置

握手超时设置

当网关与后端 HTTPS 服务通信时,会进行 TLS 握手过程。可以配置相关的超时参数:

yaml
spring:
  cloud:
    gateway:
      httpclient:
        ssl:
          handshake-timeout-millis: 10000 # TLS 握手超时时间(默认 10 秒)
          close-notify-flush-timeout-millis: 3000 # 关闭通知刷新超时(默认 3 秒)
          close-notify-read-timeout-millis: 0 # 关闭通知读取超时(默认 0,表示无限等待)

TLS 握手流程图

完整配置示例

实际业务场景配置

以下是一个电商系统网关的完整 SSL 配置示例:

yaml
server:
  port: 8443 # HTTPS 端口
  ssl:
    enabled: true
    key-alias: ecommerce-gateway
    key-store-password: ${SSL_KEYSTORE_PASSWORD} # 从环境变量获取
    key-store: classpath:certs/gateway.p12
    key-store-type: PKCS12
    protocol: TLS
    enabled-protocols: TLSv1.2,TLSv1.3 # 只允许安全的 TLS 版本

spring:
  cloud:
    gateway:
      httpclient:
        ssl:
          # 生产环境使用特定的信任证书
          trustedX509Certificates:
            - classpath:certs/user-service.pem
            - classpath:certs/order-service.pem
            - classpath:certs/payment-service.pem
          # TLS 握手超时配置
          handshake-timeout-millis: 15000
          close-notify-flush-timeout-millis: 5000
          close-notify-read-timeout-millis: 3000
      routes:
        - id: user-service
          uri: https://user-service:8443 # 后端服务也使用 HTTPS
          predicates:
            - Path=/api/users/**
        - id: order-service
          uri: https://order-service:8443
          predicates:
            - Path=/api/orders/**
kotlin
@Configuration
@EnableConfigurationProperties(GatewaySslProperties::class)
class GatewaySecurityConfiguration(
    private val sslProperties: GatewaySslProperties
) {

    /**
     * 自定义 HTTP 客户端配置
     */
    @Bean
    fun httpClientCustomizer(): HttpClientCustomizer {
        return HttpClientCustomizer { httpClient ->
            httpClient.secure { sslSpec ->
                // 配置 SSL 上下文
                sslSpec.sslContext(createSslContext())

                // 配置握手超时
                sslSpec.handshakeTimeout(Duration.ofMillis(sslProperties.handshakeTimeoutMillis))
            }
        }
    }

    /**
     * 创建 SSL 上下文
     */
    private fun createSslContext(): SslContext {
        return SslContextBuilder.forClient()
            .trustManager(loadTrustedCertificates())
            .protocols("TLSv1.2", "TLSv1.3")  // 只允许安全的协议版本
            .build()
    }

    /**
     * 加载信任的证书
     */
    private fun loadTrustedCertificates(): Array<X509Certificate> {
        return sslProperties.trustedCertificates.map { certPath ->
            // 从类路径加载证书文件
            val certResource = ResourceUtils.getFile("classpath:$certPath")
            val certFactory = CertificateFactory.getInstance("X.509")
            certFactory.generateCertificate(certResource.inputStream()) as X509Certificate
        }.toTypedArray()
    }
}

/**
 * SSL 配置属性
 */
@ConfigurationProperties(prefix = "spring.cloud.gateway.httpclient.ssl")
data class GatewaySslProperties(
    val handshakeTimeoutMillis: Long = 10000,
    val closeNotifyFlushTimeoutMillis: Long = 3000,
    val closeNotifyReadTimeoutMillis: Long = 0,
    val trustedCertificates: List<String> = emptyList()
)

安全最佳实践

1. 证书管理

IMPORTANT

  • 使用由受信任的证书颁发机构(CA)签发的证书
  • 定期更新证书,避免过期
  • 使用强加密算法(RSA 2048 位或 ECC P-256)

2. 协议版本

yaml
server:
  ssl:
    enabled-protocols: TLSv1.2,TLSv1.3 # 禁用不安全的旧版本协议
    ciphers: # 指定安全的加密套件
      - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
      - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256

3. 监控和日志

kotlin
@Component
class SslHealthIndicator : HealthIndicator {

    /**
     * 检查 SSL 证书状态
     */
    override fun health(): Health {
        return try {
            val certificateExpiry = checkCertificateExpiry()
            val daysUntilExpiry = ChronoUnit.DAYS.between(LocalDateTime.now(), certificateExpiry)

            when {
                daysUntilExpiry < 7 -> Health.down()
                    .withDetail("message", "证书将在 $daysUntilExpiry 天后过期")
                    .build()

                daysUntilExpiry < 30 -> Health.up()
                    .withDetail("warning", "证书将在 $daysUntilExpiry 天后过期")
                    .build()

                else -> Health.up()
                    .withDetail("certificate_expiry", certificateExpiry)
                    .build()
            }
        } catch (e: Exception) {
            Health.down()
                .withDetail("error", "无法检查证书状态: ${e.message}")
                .build()
        }
    }

    private fun checkCertificateExpiry(): LocalDateTime {
        // 实现证书到期时间检查逻辑
        // ...
        return LocalDateTime.now().plusDays(90) // 示例返回值
    }
}
yaml
logging:
  level:
    io.netty.handler.ssl: DEBUG # SSL 握手日志
    reactor.netty.http.client: DEBUG # HTTP 客户端日志
    org.springframework.cloud.gateway: INFO # 网关日志

常见问题和解决方案

1. 证书验证失败

错误:`javax.net.ssl.SSLHandshakeException: PKIX path building failed`

解决方案:

kotlin
// 在测试环境中暂时禁用主机名验证
@Bean
fun httpClientCustomizer(): HttpClientCustomizer {
    return HttpClientCustomizer { httpClient ->
        httpClient.secure { sslSpec ->
            sslSpec.sslContext(
                SslContextBuilder.forClient()
                    .trustManager(InsecureTrustManagerFactory.INSTANCE)  // 仅测试环境
                    .build()
            )
        }
    }
}

2. 握手超时

如果经常出现握手超时,可以适当增加超时时间,同时检查网络连接质量。

yaml
spring:
  cloud:
    gateway:
      httpclient:
        ssl:
          handshake-timeout-millis: 30000 # 增加到 30 秒

3. 性能优化

对于高并发场景,可以配置连接池和 SSL 会话缓存:

kotlin
@Bean
fun connectionProvider(): ConnectionProvider {
    return ConnectionProvider.builder("gateway-pool")
        .maxConnections(1000)                    // 最大连接数
        .maxIdleTime(Duration.ofSeconds(30))     // 空闲超时
        .maxLifeTime(Duration.ofMinutes(5))      // 连接生命周期
        .pendingAcquireTimeout(Duration.ofSeconds(10))  // 获取连接超时
        .build()
}

总结

Spring Cloud Gateway 的 TLS/SSL 配置涵盖了网关自身的 HTTPS 监听和与后端服务的安全通信两个方面。在实际应用中,需要根据环境特点选择合适的配置策略:

  • 开发环境:可以使用自签名证书和宽松的信任策略
  • 测试环境:使用与生产环境类似的证书配置
  • 生产环境:必须使用正式的 CA 证书和严格的安全策略

通过合理的 SSL/TLS 配置,可以确保微服务网关的通信安全,为整个系统提供坚实的安全保障。

Details

扩展阅读