Spring Cloud Gateway未转发客户端证书致请求失败问题咨询
嗨,我来帮你解决这个问题!你遇到的certificate_unknown问题确实是因为Spring Cloud Gateway默认不会自动将客户端证书转发给后端服务,但这不是缺失的功能,只需要通过正确的配置和自定义过滤器就能实现证书转发。下面是具体的解决方案:
首先,网关本身需要配置SSL并启用客户端认证,这样才能接收到客户端发送的证书。在你的application.yml(或application.properties)中添加以下配置:
server: ssl: enabled: true # 网关自身的服务端证书 key-store: classpath:gateway-server.jks key-store-password: your-server-cert-password # 信任库,需要包含客户端证书的CA证书,用于验证客户端证书的有效性 trust-store: classpath:gateway-truststore.jks trust-store-password: your-truststore-password # 要求客户端必须提供证书 client-auth: need
这里的trust-store必须包含签发客户端证书的CA,否则网关会直接拒绝未信任的客户端请求。
接下来需要编写一个自定义的Gateway过滤器,从请求中提取客户端证书,并将其传递给后端服务。通常有两种传递方式:
方式1:通过请求头传递证书(推荐)
这种方式简单易实现,后端服务可以从请求头中提取证书并进行验证。编写过滤器代码:
@Component public class ClientCertForwardFilter implements GatewayFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 从请求的SSL信息中获取客户端证书 X509Certificate[] clientCerts = exchange.getRequest().getSslInfo() .map(SslInfo::getPeerCertificates) .orElse(new X509Certificate[0]); if (clientCerts.length > 0) { try { // 将证书编码为Base64字符串,放到自定义请求头中 String encodedCert = new String(Base64.getEncoder().encode(clientCerts[0].getEncoded())); ServerHttpRequest modifiedRequest = exchange.getRequest().mutate() .header("X-Client-Certificate", encodedCert) .build(); return chain.filter(exchange.mutate().request(modifiedRequest).build()); } catch (CertificateEncodingException e) { return Mono.error(new RuntimeException("Failed to encode client certificate", e)); } } // 如果没有证书,直接拒绝请求(根据你的业务需求调整) return Mono.error(new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Client certificate missing")); } @Override public int getOrder() { // 设置过滤器优先级,确保在路由转发前执行 return -1; } }
然后在你的路由配置中引用这个过滤器(如果是全局过滤器可以不用单独配置):
spring: cloud: gateway: routes: - id: backend-service-route uri: https://your-backend-service:port predicates: - Path=/backend/** filters: - name: ClientCertForwardFilter
方式2:SSL层面传递证书(复杂场景)
如果后端服务必须要求SSL层面的客户端认证(即必须在TLS握手阶段提供证书),这种方式会更复杂:因为网关需要作为客户端向后端发起请求时,使用原始客户端的证书。但这里有个核心问题:客户端的私钥不会传递给网关,所以通常这种场景下,更推荐调整后端服务支持从请求头验证证书(即方式1),或者让网关持有一个被后端信任的固定客户端证书。
如果用方式1传递证书,后端服务需要从请求头中提取证书并进行验证。编写一个Spring Boot过滤器:
@Component public class ClientCertValidationFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String certHeader = request.getHeader("X-Client-Certificate"); if (certHeader == null) { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Client certificate not provided"); return; } try { // 解码证书并验证 byte[] certBytes = Base64.getDecoder().decode(certHeader); CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); X509Certificate clientCert = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(certBytes)); // 这里添加你的验证逻辑:比如检查证书是否在信任库、是否过期、主题是否合法等 // 示例:验证证书签名是否可信 TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); KeyStore trustStore = KeyStore.getInstance("JKS"); trustStore.load(new FileInputStream("classpath:backend-truststore.jks"), "your-truststore-password".toCharArray()); tmf.init(trustStore); for (TrustManager tm : tmf.getTrustManagers()) { ((X509TrustManager) tm).checkClientTrusted(new X509Certificate[]{clientCert}, "RSA"); } } catch (Exception e) { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid client certificate: " + e.getMessage()); return; } filterChain.doFilter(request, response); } }
Spring Cloud Gateway本身具备支持证书转发的能力,只是默认没有开启这个功能,因为不同场景的需求差异较大。通过上述配置和自定义过滤器,你就能实现客户端证书从网关到后端服务的转发,解决certificate_unknown的问题。
内容的提问来源于stack exchange,提问作者Dashy

