基于Reactive Netty的Spring Cloud Gateway受限URL双向认证实现
在Spring Cloud Gateway(Reactive Netty)实现路径级客户端证书认证方案
我来帮你梳理下替代Tomcat web.xml配置、实现路径差异化证书校验的最优方案——完全基于Reactive Netty,不用降级TLS版本,也不用折腾容易踩坑的TLS重协商。
先明确核心逻辑
你的需求本质是:全局TLS握手时允许客户端可选提供证书,再通过网关过滤器针对特定路径强制校验证书有效性,替代原Tomcat里的安全约束逻辑。
步骤1:全局TLS基础配置(设为want模式)
先在application.properties里配置SSL,把客户端认证设为want——这会让Reactive Netty接受客户端证书但不强制要求,后续我们在过滤器里做精细化校验,同时保留TLS1.3支持:
server.ssl.enabled=true server.ssl.key-store=classpath:server-keystore.jks server.ssl.key-store-password=your-keystore-pass server.ssl.trust-store=classpath:server-truststore.jks server.ssl.trust-store-password=your-truststore-pass # 允许客户端提供证书,但不强制(关键配置) server.ssl.client-auth=want # 同时支持TLS1.2和TLS1.3,不用降级 server.ssl.enabled-protocols=TLSv1.2,TLSv1.3
步骤2:自定义网关过滤器实现路径级证书校验
创建一个GatewayFilterFactory,专门针对受限路径校验客户端证书的有效性:
import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import javax.net.ssl.SSLSession; import java.security.cert.X509Certificate; @Component public class ClientCertValidationFilterFactory extends AbstractGatewayFilterFactory<ClientCertValidationFilterFactory.Config> { public ClientCertValidationFilterFactory() { super(Config.class); } @Override public GatewayFilter apply(Config config) { return (exchange, chain) -> { // 从请求的SSL信息中获取客户端证书 SSLSession sslSession = exchange.getRequest().getSslInfo().getSslSession(); X509Certificate[] clientCerts; try { clientCerts = (X509Certificate[]) sslSession.getPeerCertificates(); } catch (Exception e) { // 客户端未提供证书,直接返回401 return unauthorizedResponse(exchange); } // 校验证书有效性:这里可以扩展逻辑(比如检查信任链、有效期、CN白名单等) if (clientCerts == null || clientCerts.length == 0 || !isValidCert(clientCerts[0])) { return unauthorizedResponse(exchange); } // 证书有效,继续转发请求 return chain.filter(exchange); }; } private Mono<Void> unauthorizedResponse(ServerWebExchange exchange) { exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } private boolean isValidCert(X509Certificate cert) { try { // 基础校验:检查证书有效期 cert.checkValidity(); // 可选:结合信任库验证证书是否被信任,或者校验证书主题/颁发者 return true; } catch (Exception e) { return false; } } // 可以添加自定义配置参数,比如允许的证书主题前缀 public static class Config {} }
步骤3:路由配置区分公开/受限路径
在application.yml里配置网关路由,给受限路径绑定上面的过滤器,公开路径直接放行:
spring: cloud: gateway: routes: # 公开路径:无需证书校验 - id: public-legacy-routes uri: lb://your-legacy-app predicates: - Path=/rest/public/** filters: - StripPrefix=1 # 受限路径:强制证书校验 - id: secure-legacy-routes uri: lb://your-legacy-app predicates: - Path=/rest/secure/** filters: - StripPrefix=1 - ClientCertValidation
步骤4:清理遗留应用配置
现在网关已经接管了所有安全校验和HTTPS强制,你可以完全移除Tomcatweb.xml里的<security-constraint>、<login-config>、<security-role>相关配置,让遗留应用专注处理业务逻辑。
额外补充:全局HTTPS强制跳转
对应原web.xml里/*的CONFIDENTIAL约束,添加一个全局过滤器把所有HTTP请求重定向到HTTPS:
import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; @Component public class HttpsRedirectFilter implements GlobalFilter { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { if (!exchange.getRequest().isSecure()) { String httpsUrl = "https://" + exchange.getRequest().getHeaders().getHost() + exchange.getRequest().getPath(); exchange.getResponse().setStatusCode(HttpStatus.PERMANENT_REDIRECT); exchange.getResponse().getHeaders().setLocation(java.net.URI.create(httpsUrl)); return exchange.getResponse().setComplete(); } return chain.filter(exchange); } }
为什么这个方案比TLS重协商更好
- 兼容性拉满:同时支持TLS1.2和TLS1.3,不用依赖浏览器的重协商支持
- 实现更简洁:不用手动操作Reactive Netty底层的
SslHandler,避免各种诡异的会话状态问题 - 扩展性更强:可以在过滤器里灵活添加证书校验逻辑(比如角色映射、白名单),完全对齐原Tomcat的
certificate角色逻辑 - 性能更优:全局TLS握手一次完成,没有重协商的额外开销
内容的提问来源于stack exchange,提问作者jyoung
相关产品推荐
相关产品推荐

