Springboot服務(wù)HTTP/HTTPS雙監(jiān)聽(tīng)及路由的實(shí)現(xiàn)示例
背景
一般來(lái)說(shuō)SpringCloud Gateway到后面服務(wù)的路由屬于內(nèi)網(wǎng)交互,因此路由方式是否是Https就顯得不是那么重要了。事實(shí)上也確實(shí)如此,大多數(shù)的應(yīng)用開(kāi)發(fā)時(shí)基本都是直接Http就過(guò)去了,不會(huì)一開(kāi)始就是直接上Https。然而隨著時(shí)間的推移,項(xiàng)目規(guī)模的不斷擴(kuò)大,當(dāng)被要求一定要走Https時(shí),就會(huì)面臨一種困惑:將所有服務(wù)用一刀切的方式改為Https方式監(jiān)聽(tīng),同時(shí)還要將網(wǎng)關(guān)服務(wù)所有的路由方式也全部切為Https方式,一旦生產(chǎn)環(huán)境上線(xiàn)出問(wèn)題將要面臨全量服務(wù)的歸滾,這時(shí)運(yùn)維很可能跳出來(lái)說(shuō):生產(chǎn)環(huán)境幾十個(gè)服務(wù),每個(gè)服務(wù)最少2個(gè)節(jié)點(diǎn),全量部署和回滾不可能在短時(shí)間完成。另外測(cè)試同學(xué)也可能會(huì)說(shuō),現(xiàn)在沒(méi)有全量接口自動(dòng)化回歸測(cè)試工具,做一個(gè)次人工的全量接口回歸測(cè)試也不現(xiàn)實(shí)。因此在這種情況下最穩(wěn)妥的方式是實(shí)現(xiàn):SpringCloud Gateway & SpringBoot RestController Http/Https雙支持,這樣可以做到分批分次進(jìn)行切換,那么上面的困惑自然也就不存在了。
1. SpringBoot Http/Https監(jiān)聽(tīng)雙支持
1.1 代碼實(shí)現(xiàn)
為了不對(duì)原來(lái)的Http監(jiān)聽(tīng)產(chǎn)生任何影響,因此需要保障以下兩點(diǎn):
1、原主端口(server.port)監(jiān)聽(tīng)什么都不變,監(jiān)聽(tīng)方式仍為http,附加端口監(jiān)聽(tīng)方式為https。(需要繞開(kāi)的問(wèn)題是:如果一個(gè)服務(wù)有多個(gè)監(jiān)聽(tīng)端口,主端口會(huì)優(yōu)先選擇https方式)
2、附加端口不進(jìn)行nacos服務(wù)注冊(cè)(主要的考慮點(diǎn)還是不對(duì)原來(lái)的http監(jiān)聽(tīng)和路由產(chǎn)生任何影響,這里我的方案是https監(jiān)聽(tīng)端口號(hào)為http端口+10000)。
這樣就能實(shí)現(xiàn)SpringBoot服務(wù)主端口Http監(jiān)聽(tīng),附加端口Https監(jiān)聽(tīng)。
實(shí)現(xiàn)代碼如下:
import org.apache.catalina.connector.Connector;
import org.apache.coyote.http11.Http11NioProtocol;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* HttpsConnectorAddInConfiguration
*
* @author chenx
*/
@Configuration
public class HttpsConnectorAddInConfiguration {
private static final int HTTPS_PORT_OFFSET = 10000;
@Value("${server.port}")
private int port;
@Value("${additional-https-connector.ssl.key-store:XXX.p12}")
private String keyStore;
@Value("${additional-https-connector.ssl.key-store-password:XXX}")
private String keyStorePassword;
@Value("${additional-https-connector.ssl.key-store-type:PKCS12}")
private String keyStoreType;
@Value("${additional-https-connector.ssl.enabled:false}")
private boolean enabled;
@Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> servletContainer() {
return server -> {
if (!this.enabled) {
return;
}
Connector httpsConnector = this.createHttpsConnector();
server.addAdditionalTomcatConnectors(httpsConnector);
};
}
/**
* createHttpsConnector
*
* @return
*/
private Connector createHttpsConnector() {
Connector connector = new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL);
connector.setScheme("https");
connector.setPort(this.port + HTTPS_PORT_OFFSET);
connector.setSecure(true);
Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
protocol.setSSLEnabled(true);
protocol.setKeystoreFile(this.keyStore);
protocol.setKeystorePass(this.keyStorePassword);
protocol.setKeystoreType(this.keyStoreType);
protocol.setSslProtocol("TLS");
return connector;
}
}
備注:
1、上述代碼中的配置默認(rèn)值大家自行修改(key-store:XXX.p12,key-store-password:XXX),如果覺(jué)得配置additional-https-connector相關(guān)配置命名不合適也可自行修改。當(dāng)配置好additional-https-connector相關(guān)配置(additional-https-connector.ssl.enabled是一個(gè)https附加端口監(jiān)聽(tīng)的開(kāi)關(guān)),啟動(dòng)服務(wù)就可以看到類(lèi)似如下的日志,同時(shí)查看nacos中的服務(wù)實(shí)例也會(huì)發(fā)現(xiàn)并沒(méi)有進(jìn)行https端口的服務(wù)注冊(cè);
2、這里我用的是p12自簽證書(shū),證書(shū)需要放到項(xiàng)目的resouces目錄下(可以用keytool -genkey命令去生成一個(gè))。

1.2 配置
配置示例如下,keyStore、keyStorePassword、keyStoreType使用代碼中的默認(rèn)值,需要更換證書(shū)的時(shí)候再進(jìn)行配置。
server:
port: 9021
tomcat:
min-spare-threads: 400
max-threads: 800
additional-https-connector:
ssl:
enabled: true
2. SpringCloud Gateway Http/Https路由雙支持
思路:在網(wǎng)關(guān)服務(wù)增加自定義配置(HttpsServiceConfig)來(lái)定義需要切換為https路由的服務(wù)列表,然后使用過(guò)濾器(HttpsLoadBalancerFilter)進(jìn)行轉(zhuǎn)發(fā)uri的https重寫(xiě);
這樣就能實(shí)現(xiàn)在配置列表中的服務(wù)進(jìn)行Https路由,否則保持原有Https路由。
2.1 代碼實(shí)現(xiàn)
- HttpsServiceConfig
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.endpoint.event.RefreshEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* HttpsServiceConfig
*
* @author chenx
*/
@Slf4j
@Component
@RefreshScope
@ConfigurationProperties(prefix = "bw.gateway")
public class HttpsServiceConfig {
private List<String> httpsServices;
private Set<String> httpsServiceSet = new HashSet<>();
public List<String> getHttpsServices() {
return this.httpsServices;
}
public void setHttpsServices(List<String> httpsServices) {
this.httpsServices = httpsServices;
this.updateHttpsServices();
}
public Set<String> getHttpsServiceSet() {
return this.httpsServiceSet;
}
/**
* handleRefreshEvent
*/
@EventListener(RefreshEvent.class)
public void handleRefreshEvent() {
this.updateHttpsServices();
}
/**
* updateHttpsServices
*/
private void updateHttpsServices() {
this.httpsServiceSet = CollectionUtils.isNotEmpty(this.httpsServices) ? new HashSet<>(this.httpsServices) : new HashSet<>();
log.info("httpsServiceSet updated, httpsServiceSet.size() = {}", this.httpsServiceSet.size());
}
}
- HttpsLoadBalancerFilter
import com.beam.work.gateway.common.FilterEnum;
import com.beam.work.gateway.config.HttpsServiceConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;
import java.net.URI;
import java.util.Objects;
/**
* HttpsLoadBalancerFilter
*
* @author chenx
*/
@Slf4j
@RefreshScope
@Component
public class HttpsLoadBalancerFilter implements GlobalFilter, Ordered {
private static final int HTTPS_PORT_OFFSET = 10000;
private final LoadBalancerClient loadBalancer;
@Autowired
private HttpsServiceConfig httpsServiceConfig;
public HttpsLoadBalancerFilter(LoadBalancerClient loadBalancer) {
this.loadBalancer = loadBalancer;
}
@Override
public int getOrder() {
return FilterEnum.HTTPS_LOAD_BALANCER_FILTER.getCode();
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
boolean isRewriteToHttps = Objects.nonNull(route) && this.httpsServiceConfig.getHttpsServiceSet().contains(route.getId());
if (isRewriteToHttps) {
ServiceInstance instance = this.loadBalancer.choose(route.getUri().getHost());
if (Objects.nonNull(instance)) {
URI originalUri = exchange.getRequest().getURI();
URI httpsUri = UriComponentsBuilder.fromUri(originalUri)
.scheme("https")
.host(instance.getHost())
.port(instance.getPort() + HTTPS_PORT_OFFSET)
.build(true)
.toUri();
log.info("HttpsLoadBalancerFilter RewriteToHttps: {}", httpsUri.toString());
exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, httpsUri);
}
}
return chain.filter(exchange);
}
}
備注:
1、這里實(shí)現(xiàn)了配置的刷新,因此需要進(jìn)行服務(wù)的https路由切換時(shí)只需修改配置即可,而網(wǎng)關(guān)服務(wù)不需要重啟;
2、過(guò)濾器使用Set進(jìn)行判斷,效率上肯定優(yōu)于對(duì)List的遍歷查找;
3、過(guò)濾器的Order建議放到最后,因此可以直接使用Integer.MAX_VALUE(我們的項(xiàng)目中有多個(gè)過(guò)濾器,并且通過(guò)FilterEnum枚舉去統(tǒng)一管理);
2.2 配置
配置示例:
spring:
cloud:
gateway:
enabled: true
httpclient:
ssl:
use-insecure-trust-manager: true
connect-timeout: 10000
response-timeout: 120000
pool:
max-idle-time: 15000
max-life-time: 45000
evictionInterval: 5000
routes:
- id: bw-star-favorite
uri: lb://bw-star-favorite
order: -1
predicates:
- Path=/star-favoritear/v1/**
bw:
gateway:
xssRequestFilterEnable: false
xssResponseFilterEnable: false
httpsServices:
- bw-star-favorite
備注:
1、需要變更的配置為:
- 開(kāi)啟ssl信任(spring.cloud.gateway.httpclient.ssl):
- 設(shè)置https路由服務(wù)列表(bw.gateway.httpsServices)

結(jié)束語(yǔ)
通過(guò)上述兩步就能實(shí)現(xiàn)SpringCloud Gateway & SpringBoot RestController Http/Https雙支持,嚴(yán)謹(jǐn)?shù)淖龇ㄊ沁€需要將FeignClient的調(diào)用進(jìn)行Https化,上面的實(shí)現(xiàn)方式中之所以不對(duì)https端口進(jìn)行注冊(cè)的原因就是避免Http方式的FeignClient去調(diào)用Https目標(biāo)端口從而引發(fā)問(wèn)題。關(guān)于FeignClient的Https切換實(shí)際上也可以借鑒網(wǎng)關(guān)的思路將請(qǐng)求uri重寫(xiě)為端口號(hào)+10000的https請(qǐng)求即可。
那么通過(guò)這個(gè)思路就可以實(shí)現(xiàn):服務(wù)的分批、FeignClient分步Https路由切換,從而保障整個(gè)割接風(fēng)險(xiǎn)可控和平滑。
到此這篇關(guān)于Springboot服務(wù)HTTP/HTTPS雙監(jiān)聽(tīng)及路由的實(shí)現(xiàn)示例的文章就介紹到這了,更多相關(guān)Springboot HTTP/HTTPS雙監(jiān)聽(tīng)及路由內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java中&&與?表達(dá)式結(jié)合時(shí)出現(xiàn)的坑
這篇文章主要給大家介紹了關(guān)于Java中&&與?表達(dá)式結(jié)合時(shí)出現(xiàn)的坑的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-02-02
java實(shí)現(xiàn)左旋轉(zhuǎn)字符串
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)左旋轉(zhuǎn)字符串,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-03-03
Java中AuthorizationFilter過(guò)濾器的功能
本文主要介紹了Java中AuthorizationFilter過(guò)濾器的功能,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2026-02-02
spring-boot報(bào)錯(cuò)javax.servlet.http不存在的問(wèn)題解決
當(dāng)springboot項(xiàng)目從2.7.x的升級(jí)到3.0.x的時(shí)候,會(huì)遇到j(luò)avax.servlet.http不存在,本文就來(lái)介紹一下這個(gè)問(wèn)題的解決,具有一定的參考價(jià)值,感興趣的可以了解一下2024-06-06
通過(guò)springboot發(fā)布WebService接口并調(diào)用方式
Spring Boot集成CXF需注意版本對(duì)應(yīng),配置注解并發(fā)布服務(wù),通過(guò)WSDL驗(yàn)證,結(jié)合Controller和Swagger測(cè)試,CXF支持SOAP、REST等服務(wù),提供代碼與合同優(yōu)先開(kāi)發(fā)模式2025-09-09
Java ThreadLocal類(lèi)應(yīng)用實(shí)戰(zhàn)案例分析
這篇文章主要介紹了Java ThreadLocal類(lèi)應(yīng)用,結(jié)合具體案例形式分析了java ThreadLocal類(lèi)的功能、原理、用法及相關(guān)操作注意事項(xiàng),需要的朋友可以參考下2019-09-09
JDK8中String的intern()方法實(shí)例詳細(xì)解讀
String字符串在我們?nèi)粘i_(kāi)發(fā)中最常用的,當(dāng)然還有他的兩個(gè)兄弟StringBuilder和StringBuilder,接下來(lái)通過(guò)本文給大家介紹JDK8中String的intern()方法詳細(xì)解讀,需要的朋友可以參考下2022-09-09

