Spring Cloud Gateway配置HTTPS后,微服务调用报错NotSslRecordException?一个配置项帮你搞定
Spring Cloud Gateway HTTPS配置中的NotSslRecordException深度解析与实战解决方案
当开发者在Spring Cloud Gateway中配置HTTPS后,经常会遇到一个令人困惑的错误:NotSslRecordException。这个异常通常发生在网关将加密的HTTPS请求直接转发给下游微服务时,而微服务期望接收的是普通的HTTP请求。本文将深入分析这一问题的根源,并提供多种经过实战验证的解决方案。
1. 问题现象与根源分析
在实际生产环境中,当开发者为Spring Cloud Gateway配置HTTPS后,可能会在日志中看到如下错误堆栈:
io.netty.handler.ssl.NotSslRecordException: not an SSL/TLS record at io.netty.handler.ssl.SslHandler.decodeJdkCompatible(SslHandler.java:1178) at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1243) ...这个错误的本质原因是:客户端通过HTTPS访问网关,网关正确解密了请求,但在转发给下游微服务时,仍然保持了https协议头,而下游微服务并没有配置SSL/TLS,导致无法处理加密的请求。
关键问题点:
- 网关接收的是HTTPS请求(加密)
- 网关转发时保留了
httpsscheme - 下游微服务期望接收HTTP请求(未加密)
- Netty尝试将普通HTTP请求当作SSL/TLS记录解析
2. 核心解决方案对比
针对这一问题,主要有两种主流解决方案,各有优缺点:
| 解决方案 | 实现方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| URI显式指定HTTP | 在路由配置中明确使用lb:http:// | 配置简单,一目了然 | 需要修改所有路由配置 | 新项目或路由较少的情况 |
| 过滤器修改Scheme | 使用自定义过滤器修改请求scheme | 无需修改路由配置,集中管理 | 需要编写额外代码 | 已有大型项目迁移 |
3. 方案一:路由URI显式指定HTTP协议
这是最直接简单的解决方案,只需在路由配置中明确指定使用http协议:
spring: cloud: gateway: routes: - id: service-route uri: lb:http://service-name # 关键在此处明确使用http predicates: - Path=/api/**实现原理:
lb:http://前缀明确告知网关使用HTTP协议转发- LoadBalancerClientFilter会根据此scheme创建请求
- 下游微服务接收普通HTTP请求,避免SSL解析错误
注意事项:
- 确保所有路由配置都使用
lb:http://前缀 - 如果微服务本身也配置了HTTPS,则需要保持
lb:https:// - 在服务发现场景下,服务名(
service-name)需与注册中心一致
4. 方案二:自定义过滤器修改Scheme
对于已有大型项目,修改所有路由配置可能不现实。这时可以通过自定义过滤器统一修改请求scheme:
public class HttpSchemeFilter implements GlobalFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { URI originalUri = exchange.getRequest().getURI(); ServerHttpRequest request = exchange.getRequest(); // 只修改lb://开头的服务调用 if(originalUri != null && originalUri.getScheme().equals("lb")) { URI newUri = UriComponentsBuilder.fromUri(originalUri) .scheme("http") // 强制修改为http .build() .toUri(); ServerHttpRequest newRequest = request.mutate() .uri(newUri) .build(); return chain.filter(exchange.mutate().request(newRequest).build()); } return chain.filter(exchange); } @Override public int getOrder() { return LoadBalancerClientFilter.LOAD_BALANCER_CLIENT_FILTER_ORDER - 1; } }注册过滤器:
@Configuration public class GatewayConfig { @Bean public HttpSchemeFilter httpSchemeFilter() { return new HttpSchemeFilter(); } }技术要点:
- 过滤器需要在
LoadBalancerClientFilter之前执行 - 只修改
lb://开头的服务调用URI - 保持原始请求的其他部分不变
- 可以通过配置中心动态控制是否启用
5. 进阶:混合HTTPS/HTTP环境配置
在某些复杂场景中,可能需要网关同时支持:
- 对外HTTPS访问
- 对内HTTP服务调用
- 部分特殊服务需要HTTPS调用
这种混合环境的配置示例如下:
server: port: 8443 ssl: enabled: true key-store: classpath:keystore.p12 key-store-password: changeit key-store-type: PKCS12 spring: cloud: gateway: routes: - id: http-service uri: lb:http://http-service predicates: - Path=/http-api/** - id: https-service uri: lb:https://https-service predicates: - Path=/https-api/** - id: default-route uri: lb:http://default-service predicates: - Path=/**关键配置项:
server.ssl.enabled: 启用网关HTTPS- 按服务需求分别配置
lb:http://和lb:https:// - 默认路由建议使用HTTP,除非明确知道服务支持HTTPS
6. 排查技巧与常见误区
即使按照上述方案配置,仍可能遇到问题。以下是几个排查技巧:
检查清单:
- 确认网关确实接收的是HTTPS请求
curl -vk https://gateway:8443/api/endpoint - 检查路由配置中的URI scheme
# 错误示例 uri: lb://service-name # 正确示例 uri: lb:http://service-name - 查看网关日志中的实际转发URL
logging.level.org.springframework.cloud.gateway=DEBUG - 确认下游服务确实运行在HTTP端口
常见误区:
- 认为网关配置HTTPS后,所有通信都会自动加密(实际上网关到服务的通信需要单独配置)
- 忽略服务发现场景下的scheme继承问题
- 在Kubernetes环境中混淆Service类型(ClusterIP vs NodePort)
- 忘记在测试环境关闭证书验证(开发阶段可配置
insecure-skip-verify)
7. 性能考量与最佳实践
在实施HTTPS网关方案时,还需考虑性能影响:
性能优化建议:
- 使用现代加密算法(TLS 1.3)
server: ssl: protocol: TLSv1.3 ciphers: TLS_AES_256_GCM_SHA384,TLS_AES_128_GCM_SHA256 - 启用OCSP Stapling减少证书验证开销
- 考虑硬件SSL加速(如AWS ALB)
- 监控网关的SSL握手指标
# Netty原生指标 jcmd <pid> VM.native_memory | grep Ssl
架构建议:
- 在网关层集中管理SSL/TLS终止
- 内部服务间通信保持HTTP简化架构
- 对特别敏感的服务可启用双向TLS认证
- 定期轮换证书并自动化部署流程
通过以上方案和最佳实践,开发者可以彻底解决Spring Cloud Gateway中的NotSslRecordException问题,同时构建出既安全又高性能的微服务通信架构。
