Spring Cloud Gateway配置HTTPS后,微服务调用报错NotSslRecordException?一个配置项帮你搞定
Spring Cloud Gateway HTTPS配置中的NotSslRecordException深度解析与实战修复
当你在Spring Cloud Gateway中配置了HTTPS证书,却发现转发到后端微服务的请求抛出 NotSslRecordException 异常时,这种看似简单的配置问题背后隐藏着微服务架构中协议转换的关键机制。本文将带你深入理解这一问题的根源,并提供多种解决方案,同时分享我在实际项目中的踩坑经验。
1. 问题现象与错误解析
典型的错误场景是这样的:你已经为Spring Cloud Gateway配置了SSL证书,客户端通过HTTPS访问网关一切正常。但当请求被转发到后端微服务时,日志中会出现类似如下的异常堆栈:
io.netty.handler.ssl.NotSslRecordException: not an SSL/TLS record: 485454502f312e3120343030200d0a...
这个异常的字面意思是"不是SSL/TLS记录",它表明Netty的SSL处理器收到了一个非加密的HTTP请求,而它期望的是加密的HTTPS流量。这种情况通常发生在以下场景:
- 网关使用HTTPS接收客户端请求
- 后端微服务仅配置了HTTP端口
- 网关默认将HTTPS请求原样转发给后端服务
关键问题在于协议不匹配 :网关接收的是HTTPS请求,但转发时没有进行协议转换,导致后端服务的HTTP端口收到了它无法处理的加密流量。
2. 问题根源:LoadBalancerClientFilter的工作机制
要真正理解这个问题,我们需要深入Spring Cloud Gateway的核心组件之一—— LoadBalancerClientFilter 。这个过滤器负责将路由URI中的服务名解析为实际的服务实例地址。
默认情况下,当你的路由配置使用 lb://service-id 格式时, LoadBalancerClientFilter 会:
- 从服务注册中心获取
service-id对应的实例列表 - 选择一个实例(根据负载均衡策略)
- 将
lb://service-id替换为选中的实例地址(如http://192.168.1.100:8080)
关键点在于 :如果URI中没有明确指定协议( http 或 https ),过滤器会默认使用原始请求的协议。这就是为什么HTTPS请求会被原样转发到后端服务。
3. 解决方案:显式指定协议
最直接的解决方案是在路由配置中显式指定 lb:http://service-id ,强制使用HTTP协议转发请求:
spring:
cloud:
gateway:
routes:
- id: my-service
uri: lb:http://my-service # 关键在这里显式指定http
predicates:
- Path=/api/**
这种方案的优点是:
- 配置简单直接
- 不需要修改后端服务
- 网关仍然保持HTTPS终端的作用
我在实际项目中验证过,这种方案能有效解决 NotSslRecordException 问题。但需要注意的是,这意味着后端服务之间的通信是明文的,在安全性要求高的场景可能需要考虑其他方案。
4. 进阶方案:全链路HTTPS配置
如果你的安全需求要求全链路加密,那么需要为后端服务也配置HTTPS。这种方案更复杂但更安全,具体步骤如下:
4.1 后端服务HTTPS配置
首先,为每个微服务配置SSL:
server:
ssl:
enabled: true
key-store: classpath:keystore.p12
key-store-password: yourpassword
key-store-type: PKCS12
port: 8443
4.2 网关路由配置
然后在网关中配置使用HTTPS转发:
spring:
cloud:
gateway:
routes:
- id: secure-service
uri: lb:https://secure-service # 注意这里是https
predicates:
- Path=/secure/**
4.3 服务发现与健康检查
全链路HTTPS还需要注意:
- 服务注册时需要使用HTTPS端口
- 健康检查端点也需要支持HTTPS
- 可能需要配置SSL证书信任链
这种方案的缺点是配置复杂,且会增加系统开销。我在金融项目中采用过这种方案,确实能提供更高的安全性,但维护成本也显著增加。
5. 混合方案:HTTP与HTTPS共存
有些场景下,你可能希望网关同时支持HTTP和HTTPS访问,但后端服务只使用HTTP。这可以通过以下配置实现:
@Configuration
public class HttpToHttpsConfig {
@Value("${server.http.port}")
private int httpPort;
@Value("${server.port}")
private int httpsPort;
@PostConstruct
public void startRedirectServer() {
NettyReactiveWebServerFactory httpFactory = new NettyReactiveWebServerFactory(httpPort);
httpFactory.getWebServer((request, response) -> {
URI uri = request.getURI();
URI httpsUri = URI.create("https://" + uri.getHost() + ":" + httpsPort + uri.getPath());
response.setStatusCode(HttpStatus.PERMANENT_REDIRECT);
response.getHeaders().setLocation(httpsUri);
return response.setComplete();
}).start();
}
}
配合以下application配置:
server:
port: 8443 # HTTPS端口
http:
port: 8080 # HTTP端口
ssl:
enabled: true
key-store: classpath:keystore.p12
这种方案下,客户端无论使用HTTP还是HTTPS访问网关,都会被正确处理,而后端服务始终接收HTTP请求。
6. 排查技巧与常见陷阱
在实际项目中,即使按照上述方案配置,仍可能遇到各种边缘情况。以下是我总结的一些排查技巧:
- 检查证书链完整性 :不完整的证书链可能导致握手失败
- 验证协议版本 :确保客户端、网关和后端服务支持的TLS版本兼容
- 检查负载均衡器配置 :某些云平台的LB可能修改或终止TLS
- 日志级别调整 :将
reactor.netty和org.springframework.cloud.gateway的日志级别设为DEBUG
一个常见的陷阱是忘记在路由配置中正确使用 lb:http:// 前缀。我曾经花了两个小时排查一个问题,最后发现是手误写成了 lb:/http:// (多了一个斜杠)。
另一个容易忽略的点是服务注册时的元数据。确保你的服务注册时包含了正确的协议和端口信息:
spring:
cloud:
nacos:
discovery:
metadata:
secure: "true"
protocol: "https"
7. 性能考量与最佳实践
在实施HTTPS网关方案时,性能是需要重点考虑的因素:
- 会话恢复 :启用TLS会话票据可以减少握手开销
- OCSP Stapling :减少证书状态检查的延迟
- 证书选择 :ECDSA证书比RSA证书性能更好
- 密码套件 :选择现代的高效密码套件
在我的性能测试中,合理配置的HTTPS网关相比HTTP网关,吞吐量下降约15-20%。这个开销在大多数场景下是可以接受的,但高并发系统需要特别注意。
最佳实践建议 :
- 开发环境可以使用自签名证书简化流程
- 生产环境建议使用权威CA颁发的证书
- 定期轮换证书并监控过期时间
- 考虑使用证书管理工具自动化证书部署
8. 架构思考:何时该使用HTTPS终端模式
在微服务架构中,HTTPS终端模式(网关处理TLS,内部服务使用HTTP)是一种常见的折中方案。它适合以下场景:
- 内部网络可信度高
- 性能要求高于安全要求
- 服务间通信不涉及敏感数据
- 需要简化后端服务配置
而不适合的场景包括:
- 合规性要求全链路加密
- 多租户环境
- 跨数据中心通信
- 处理高度敏感数据
在我的经验中,电商平台通常可以采用终端模式,而金融系统则更适合全链路HTTPS。
更多推荐


所有评论(0)