原文网址:SpringCloud Gateway--支持https_IT利刃出鞘的博客-CSDN博客
说明
本文介绍SpringCloud Gateway如何支持https。
gateway在与微服务是通过http的,无论gateway配置的是http还是https,最终都会使用http与微服务通信。(zuul也是如此)。
官网
7. TLS / SSL (spring cloud gateway官网)
获取SSL证书
后台项目应用系列--https_feiying0canglang的博客-CSDN博客
gateway与其余微服务通信
application.yml
server:
port: 7001
spring:
application:
name: eureka-server
eureka:
instance:
hostname: localhost1
client:
register-with-eureka: false
fetch-registry: false
serviceUrl:
defaultZone: http://localhost:7001/eureka/
# 下边是高可用配置
#server:
# port: 7001
#
#spring:
# application:
# name: server
#
#eureka:
# instance:
# hostname: localhost1
# client:
# serviceUrl:
# defaultZone: http://localhost:7002/eureka/
#server:
# port: 7002
#
#spring:
# application:
# name: server
#
#eureka:
# instance:
# hostname: localhost2
# client:
# serviceUrl:
# defaultZone: http://localhost:7001/eureka/
pom.xml
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.1.12.RELEASE
com.example
eureka-server
0.0.1-SNAPSHOT
eureka-server
Demo project for Spring Boot
1.8
org.springframework.cloud
spring-cloud-starter-netflix-eureka-server
org.springframework.cloud
spring-cloud-dependencies
Greenwich.SR6
pom
import
application.yml
server:
port: 9002 #win10下9001端口被占用
#server:
# port: 9003
spring:
application:
name: product
eureka:
client:
service-Url:
defaultZone: http://localhost:7001/eureka
# defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka
feign:
hystrix:
enabled: true
controller
package com.example.product.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
@RequestMapping("/product")
public class ProductController {
@GetMapping("/gateway")
public String feign1(){
return "succeed";
}
}
将“简介”中获得的证书放到resources目录下。
pom.xml
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.1.12.RELEASE
com.example
demo
0.0.1-SNAPSHOT
gateway
Demo project for Spring Boot
1.8
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-test
test
org.springframework.cloud
spring-cloud-starter-gateway
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.cloud
spring-cloud-dependencies
Greenwich.SR6
pom
import
org.springframework.boot
spring-boot-maven-plugin
gateway微服务
application.yml
server:
port: 6443
ssl:
enabled: true
key-alias: tomcat
key-store: classpath:keystore.p12
key-store-password: 222333
keyStoreType: PKCS12
spring:
application:
name: gateway
cloud:
gateway:
discovery:
locator:
enabled: true
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka/
# 配置Gateway日志等级,输出转发细节信息
logging:
level:
org.springframework.cloud.gateway: debug
测试
访问:https://localhost:6443/PRODUCT/product/gateway
其他网址
后台项目应用系列--https_feiying0canglang的博客-CSDN博客 //由此配置方式获得启发
application.yml
# just http
#server:
# port: 6001
# just https
#server:
# port: 6443
# ssl:
# enabled: true
# key-alias: tomcat
# key-store: classpath:keystore.p12
# key-store-password: 222333
# keyStoreType: PKCS12
# http and https
server:
port: 6001
# custom
https:
server:
port: 6443
ssl:
enabled: true
key-alias: tomcat
key-store: classpath:keystore.p12
key-store-password: 222333
keyStoreType: PKCS12
spring:
application:
name: gateway
cloud:
gateway:
discovery:
locator:
enabled: true
httpclient:
ssl:
use-insecure-trust-manager: true
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka/
# 配置Gateway日志等级,输出转发细节信息
logging:
level:
org.springframework.cloud.gateway: debug
配置类
简洁方式
配置类
package com.example.demo.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory;
import org.springframework.boot.web.server.WebServer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.server.reactive.HttpHandler;
@Configuration
@ConditionalOnProperty(name = "https.server.ssl.enabled", havingValue = "true")
public class HttpsConfig {
@Bean
@ConfigurationProperties(prefix = "https.server")
public HttpsProperties httpsProperties() {
return new HttpsProperties();
}
@Bean(initMethod = "start", destroyMethod = "stop")
public WebServer httpWebServer(HttpHandler handler, HttpsProperties properties) {
NettyReactiveWebServerFactory factory = new NettyReactiveWebServerFactory(properties.getPort());
factory.setSsl(properties.getSsl());
return factory.getWebServer(handler);
}
}
属性类
package com.example.demo.config;
import org.springframework.boot.web.server.Ssl;
public class HttpsProperties {
private int port = 6443;
private Ssl ssl;
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public Ssl getSsl() {
return ssl;
}
public void setSsl(Ssl ssl) {
this.ssl = ssl;
}
}
复杂法
package com.example.demo.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory;
import org.springframework.boot.web.server.Ssl;
import org.springframework.boot.web.server.WebServer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.server.reactive.HttpHandler;
import java.io.File;
import java.io.IOException;
@Configuration
@ConditionalOnProperty(name = "https.server.ssl.enabled", havingValue = "true")
public class HttpsConfig {
@Value("${https.server.port:6443}")
private int httpsPort;
@Value("${https.server.ssl.key-alias:'tomcat'}")
private String keyAlias;
@Value("${https.server.ssl.key-store:'classpath:keystore.p12'}")
private String keyStore;
@Value("${https.server.ssl.key-store-password:'222333'}")
private String keyStorePassword;
@Value("${https.server.ssl.keyStoreType:'PKCS12'}")
private String keyStoreType;
@Bean(initMethod = "start", destroyMethod = "stop")
public WebServer httpWebServer(HttpHandler handler) {
NettyReactiveWebServerFactory factory = new NettyReactiveWebServerFactory(httpsPort);
Ssl ssl = new Ssl();
ssl.setEnabled(true);
ssl.setKeyAlias(keyAlias);
ssl.setKeyStore(keyStore);
ssl.setKeyStorePassword(keyStorePassword);
ssl.setKeyStoreType(keyStoreType);
factory.setSsl(ssl);
return factory.getWebServer(handler);
}
}
踩坑记录
下边这样写有问题。描述:在Idea下直接运行是可以的,但是打包成jar运行就会报错:
Caused by: java.io.FileNotFoundException: class path resource [keystore.p12] cannot be resolved to absolute file path because it does not reside in the file system: jar:file:/home/gateway-0.0.1-SNAPSHOT.jar!/BOOT-INF/classes!/keystore.p12ClassPathResource
原因:打包成jar无法读取文件,要用流读取。
application.yml
# just http
#server:
# port: 6001
# just https
#server:
# port: 6443
# ssl:
# enabled: true
# key-alias: tomcat
# key-store: classpath:keystore.p12
# key-store-password: 222333
# keyStoreType: PKCS12
# http and https
server:
port: 6001
# custom
https:
server:
port: 6443
ssl:
enabled: true
key-alias: tomcat
key-store: keystore.p12
key-store-password: 222333
keyStoreType: PKCS12
spring:
application:
name: gateway
cloud:
gateway:
discovery:
locator:
enabled: true
httpclient:
ssl:
use-insecure-trust-manager: true
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka/
# 配置Gateway日志等级,输出转发细节信息
logging:
level:
org.springframework.cloud.gateway: debug
配置类
package com.example.demo.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory;
import org.springframework.boot.web.server.Ssl;
import org.springframework.boot.web.server.WebServer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.server.reactive.HttpHandler;
import java.io.File;
import java.io.IOException;
@Configuration
@ConditionalOnProperty(name = "https.server.ssl.enabled", havingValue = "true")
public class HttpsConfig {
@Value("${https.server.port:6443}")
private int httpsPort;
@Value("${https.server.ssl.key-alias:'tomcat'}")
private String keyAlias;
@Value("${https.server.ssl.key-store:'classpath:keystore.p12'}")
private String keyStore;
@Value("${https.server.ssl.key-store-password:'222333'}")
private String keyStorePassword;
@Value("${https.server.ssl.keyStoreType:'PKCS12'}")
private String keyStoreType;
@Bean(initMethod = "start", destroyMethod = "stop")
public WebServer httpWebServer(HttpHandler handler) {
NettyReactiveWebServerFactory factory = new NettyReactiveWebServerFactory(httpsPort);
File keyStoreFile;
try {
keyStoreFile = new ClassPathResource(keyStore).getFile();
} catch (IOException ex) {
throw new IllegalStateException("can't access keystore: [" + "keystore"
+ "] or truststore: [" + "keystore" + "]", ex);
}
Ssl ssl = new Ssl();
ssl.setEnabled(true);
ssl.setKeyAlias(keyAlias);
ssl.setKeyStore(keyStoreFile.getAbsolutePath());
ssl.setKeyStorePassword(keyStorePassword);
ssl.setKeyStoreType(keyStoreType);
factory.setSsl(ssl);
return factory.getWebServer(handler);
}
}
其他网址
SpringCloud Gateway网关同时支持http和https访问_u013998466的博客-CSDN博客
Spring Cloud Gateway同时监听HTTP和HTTPS(http自动转发https端口)_jingle_1995的博客-CSDN博客
Spring cloud gateway 设置https 和http同时支持_荡漾-CSDN博客
SpringCloud Gateway网关同时支持http和https访问_u013998466的博客-CSDN博客
官网
application.yml
server:
port: 6443
ssl:
enabled: true
key-alias: tomcat
key-store: classpath:keystore.p12
key-store-password: 222333
keyStoreType: PKCS12
# custom
http:
server:
enabled: true
port: 6001
spring:
application:
name: gateway
cloud:
gateway:
discovery:
locator:
enabled: true
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka/
# 配置Gateway日志等级,输出转发细节信息
logging:
level:
org.springframework.cloud.gateway: debug
配置类
法1:创建http服务器
高级写法
package com.landsky.ener.gateway.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory;
import org.springframework.boot.web.server.WebServer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.server.reactive.HttpHandler;
@Configuration
@ConditionalOnProperty(name = "http.server.enabled", havingValue = "true")
public class HttpConfiguration {
@Value("${http.server.port:6001}")
private int httpPort;
@Bean(initMethod = "start", destroyMethod = "stop")
public WebServer httpWebServer(HttpHandler handler) {
NettyReactiveWebServerFactory factory = new NettyReactiveWebServerFactory(httpPort);
return factory.getWebServer(handler);
}
}
低级写法
package com.example.demo.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory;
import org.springframework.boot.web.server.WebServer;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.server.reactive.HttpHandler;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@Configuration
public class HttpConfig {
@Value("${http.server.port:0}")
private int httpPort;
@Value("${http.server.enabled:0}")
private Boolean httpEnabled;
@Autowired
private HttpHandler httpHandler;
private WebServer webServer;
@PostConstruct
public void start() {
if (httpEnabled) {
NettyReactiveWebServerFactory factory = new NettyReactiveWebServerFactory(httpPort);
webServer = factory.getWebServer(httpHandler);
webServer.start();
}
}
@PreDestroy
public void stop() {
webServer.stop();
}
}
法2:http转https
package com.example.demo.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import reactor.core.publisher.Mono;
import javax.annotation.PostConstruct;
import java.net.URI;
import java.net.URISyntaxException;
@Configuration
public class HttpToHttpsRedirectConfig {
@Value("${http.server.port:0}")
private int httpPort;
@Value("${http.server.enabled:0}")
private Boolean httpEnabled;
@Value("${server.port:0}")
private int httpsPort;
@Value("${server.ssl.enabled:0}")
private Boolean httpsEnabled;
@PostConstruct
public void startRedirectServer() {
if (httpEnabled) {
if (httpsEnabled) {
NettyReactiveWebServerFactory httpNettyReactiveWebServerFactory = new NettyReactiveWebServerFactory(httpPort);
httpNettyReactiveWebServerFactory.getWebServer((request, response) -> {
URI uri = request.getURI();
URI httpsUri;
try {
httpsUri = new URI("https", uri.getUserInfo(), uri.getHost(), httpsPort, uri.getPath(), uri.getQuery(), uri.getFragment());
} catch (URISyntaxException e) {
return Mono.error(e);
}
response.setStatusCode(HttpStatus.MOVED_PERMANENTLY);
response.getHeaders().setLocation(httpsUri);
return response.setComplete();
}).start();
}
}
}
}
测试
访问http:http://localhost:6001/PRODUCT/product/gateway
访问https:https://localhost:6443/PRODUCT/product/gateway
其他网址
【原创】解决WSS报错:WebSocket connection failed: Error in connection establishment: net::ERR_CERT_AUTHORITY_INVALID - 金牛座, 爬山虎
简介
配置支持https后,就会支持wss(websocket https)。
本处修改“公共代码”进行测试。(本处直接支持ws与wss(与支持http与https配置是一样的))
product
websockt工具类
package com.example.product.util;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
@Component
@ServerEndpoint(value = "/ws/{token}")
public class WebSocketServer {
//静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
private static int onlineCount = 0;
//保存客户端所对应的WebSocketServer
private static Map clientMap = new ConcurrentHashMap<>();
private String token;
//与某个客户端的连接会话,需要通过它来给客户端发送数据
private Session session;
// 连接建立成功调用的方法
@OnOpen
public void onOpen(@PathParam("token") String token, Session session) {
//TODO 校验token
this.session = session;
addOnlineCount();
clientMap.put(token, this);
log.info("新连接加入!" + " token:" + token + "; session.getId():" + session.getId() + " 当前连接数:" + onlineCount);
}
// 连接关闭
@OnClose
public void onClose() {
subOnlineCount();
clientMap.remove(token);
log.info("有一连接关闭,当前连接数为:" + onlineCount);
}
// 收到客户端消息
@OnMessage
public void onMessage(String message, Session session) throws IOException {
log.info("来自客户端的消息:" + message);
sendMsgToAll(message);
}
// 发生错误
@OnError
public void onError(Session session, Throwable error) {
log.info("发生错误!");
error.printStackTrace();
}
public void sendMessage(String token, String message) throws IOException {
if (!StringUtils.isEmpty(token) && clientMap.containsKey(token)) {
clientMap.get(token).send(message);
log.info("成功发送一条消息:" + message);
} else {
log.error("用户:" + token + ",不在线!");
}
}
public void send(String message) throws IOException{
this.session.getBasicRemote().sendText(message);
}
// 给所有客户端群发消息
public void sendMsgToAll(String message) throws IOException {
for (WebSocketServer item : clientMap.values()) {
item.session.getBasicRemote().sendText(message);
}
log.info("成功群发一条消息:" + onlineCount);
}
public static synchronized int getOnlineCount() {
return WebSocketServer.onlineCount;
}
public static synchronized void addOnlineCount() {
WebSocketServer.onlineCount++;
}
public static synchronized void subOnlineCount() {
WebSocketServer.onlineCount--;
}
}
websocket配置类
package com.example.product.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
测试
用此在线网址测试:websocket/ws/wss在线调试测试工具
测试ws
连接到:ws://127.0.0.1:6001/PRODUCT/ws/1
测试wss
连接到:wss://127.0.0.1:6443/PRODUCT/ws/1
连接失败!
后台打印:
io.netty.handler.codec.DecoderException: javax.net.ssl.SSLException: Received fatal alert: certificate_unknown
打开F12,重新连一下
解决方法:
1. 打开 Chrome,新开一个Tab页面。
2. 访问自己的测试域名(wss替换为https):https://127.0.0.1:6443/PRODUCT/ws/1。
3. 浏览器告警:"您的连接不是私密连接......."。
4. 点"高级",继续点击 "继续前往 www.wss.com(不安全)"。
5. 页面提示"400 Bad Request......"。不用理会,这是因为用HTTP协议访问WSS服务。
此时重新连接wss://127.0.0.1:6443/PRODUCT/ws/1
疑问及解答
问:对于自己颁发的证书,为了使wss连接成功,每一个wss都要这样改为https然后访问一下吗?
答:不是的。只要访问了其对应的https、域名(ip)、端口的一个网址,同一https/wss+域名(ip)+端口和的网址/websocket连接全都可以正常了。比如:登录时用的是https,之后所有同一https/wss+域名(ip)+端口的网址/websocket连接就全部可以用了,而项目里一般websocket的域名(ip)+端口与接口(比如登录)是一样的,所以所有wss都正常连接了。
比较老的gateway版本不支持将https转为http然后与微服务通信,解决方法见下方参考网址
SpringCloud Gateway Https设置 以Http 转发 路由 给后台微服务_jingle_1995的博客-CSDN博客
spring-cloud-gateway使用https注意事项2---如何在转发后端服务的时候使用http - 简书