基于Netty与Spring RestController的双向TLS场景获取客户端SSL证书
双向TLS场景下获取客户端证书及Netty通道信息(Spring WebFlux 2.7.4)
一、先确认双向TLS配置生效
不管是通过代码配置Reactor Netty,还是用Spring Boot配置文件,必须开启强制客户端认证:
方式1:Spring Boot配置文件(application.yml)
server: ssl: enabled: true key-store: classpath:server.jks # 服务端证书库 key-store-password: your-server-keystore-pwd key-password: your-server-key-pwd trust-store: classpath:client-truststore.jks # 存放信任的客户端CA证书 trust-store-password: your-truststore-pwd client-auth: required # 强制要求客户端提供证书
方式2:代码配置Reactor Netty
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import reactor.netty.http.server.HttpServer; import reactor.netty.tls.TlsServerSpec; @Configuration public class NettyTlsConfig { @Bean public HttpServer httpServer() { return HttpServer.create() .secure(spec -> spec .sslContextBuilder(sslCtxBuilder -> { // 加载服务端证书、私钥及信任的客户端CA sslCtxBuilder.keyManager("server.crt", "server.key") .trustManager("client-ca.crt"); }) .clientAuth(TlsServerSpec.ClientAuth.REQUIRED)); } }
二、在RestController中获取客户端SSL证书
通过ServerHttpRequest拿到SSL会话,进而提取客户端证书的subjectAltName字段:
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.http.server.reactive.ServerHttpRequest; import javax.net.ssl.SSLSession; import java.security.cert.X509Certificate; import java.util.Optional; @RestController public class SecureApiController { @GetMapping("/api/verify-client") public String verifyClientCert(ServerHttpRequest request) { SSLSession sslSession = request.getSslInfo().getSession(); try { // 获取客户端证书链,第一个元素是客户端自身证书 X509Certificate[] clientCerts = (X509Certificate[]) sslSession.getPeerCertificates(); if (clientCerts.length == 0) { throw new SecurityException("未获取到客户端证书"); } X509Certificate clientCert = clientCerts[0]; // 提取DNS类型的subjectAltName(类型标识2),按需调整类型 Optional<String> sanDns = clientCert.getSubjectAlternativeNames().stream() .filter(entry -> (int) entry.get(0) == 2) .map(entry -> entry.get(1).toString()) .findFirst(); if (sanDns.isEmpty()) { throw new SecurityException("客户端证书无有效DNS类型的subjectAltName"); } // 执行subjectAltName验证逻辑 if (!"allowed-client.example.com".equals(sanDns.get())) { throw new SecurityException("客户端证书subjectAltName验证不通过"); } return String.format("客户端证书验证通过,subjectAltName: %s", sanDns.get()); } catch (Exception e) { throw new SecurityException("客户端证书验证失败", e); } } }
三、获取Netty通道(Channel)信息
通过ServerWebExchange获取底层Netty请求实例,进而拿到通道详情:
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.server.ServerWebExchange; import reactor.netty.http.server.HttpServerRequest; import reactor.netty.channel.ChannelOperations; @RestController public class ChannelInfoController { @GetMapping("/api/channel-details") public String getChannelInfo(ServerWebExchange exchange) { // 获取底层Netty请求对象 HttpServerRequest nettyRequest = exchange.getNativeRequest(HttpServerRequest.class); if (nettyRequest == null) { return "无法获取Netty请求实例"; } ChannelOperations<?, ?> channelOps = nettyRequest.channelOperations(); String channelId = channelOps.channel().id().asShortText(); String remoteAddr = channelOps.channel().remoteAddress().toString(); return String.format("通道ID: %s\n客户端远程地址: %s", channelId, remoteAddr); } }
关键注意点
- 服务端信任库必须包含客户端证书的签发CA,否则TLS握手直接失败
getPeerCertificates()会在客户端未提供证书时抛出SSLPeerUnverifiedException,需捕获处理- subjectAltName支持多种类型(DNS=2、IP=7等),根据业务需求调整过滤规则
- 代码适配Spring Boot 2.7.4及对应Reactor Netty 1.0.x版本
内容的提问来源于stack exchange,提问作者user3915656
相关产品推荐
相关产品推荐

