0
点赞
收藏
分享

微信扫一扫

SpringCloud Gateway--支持https

M4Y 2022-03-23 阅读 28


简介

说明

        ​本文介绍SpringCloud Gateway如何支持https。

         gateway在与微服务是通过http的,无论gateway配置的是http还是https,最终都会使用http与微服务通信。(zuul也是如此)。

官网

​​7. TLS / SSL (spring cloud gateway官网)​​

获取SSL证书

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

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<!-- <version>2.0.6.RELEASE</version>-->
<version>2.1.12.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>eureka-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>eureka-server</name>
<description>Demo project for Spring Boot</description>

<properties>
<java.version>1.8</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<!-- <version>Finchley.SR2</version>-->
<version>Greenwich.SR6</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>

product

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";
}
}

gateway

将“简介”中获得的证书放到resources目录下。

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.12.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>gateway</name>
<description>Demo project for Spring Boot</description>

<properties>
<java.version>1.8</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR6</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

接口请求

仅支持https

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​​

SpringCloud Gateway--支持https_https

同时支持https与http(https采用代码方式)

其他网址

//由此配置方式获得启发

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);
}
}

同时支持https与http(http采用代码方式)

​其他网址​

SpringCloud Gateway网关同时支持http和https访问_u013998466的博客

Spring Cloud Gateway同时监听HTTP和HTTPS(http自动转发https端口)_jingle_1995的博客

Spring cloud gateway 设置https 和http同时支持_荡漾-

SpringCloud Gateway网关同时支持http和https访问_u013998466的博客-

​​官网​​

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​​

SpringCloud Gateway--支持https_.net_02

访问https:​​https://localhost:6443/PRODUCT/product/gateway​​

SpringCloud Gateway--支持https_java_03

http请求转为https请求(待解决)

Websocket请求

其他网址

​​【原创】解决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<String, WebSocketServer> 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

SpringCloud Gateway--支持https_.net_04

测试wss

 连接到:wss://127.0.0.1:6443/PRODUCT/ws/1

SpringCloud Gateway--支持https_spring_05

连接失败!

后台打印:

io.netty.handler.codec.DecoderException: javax.net.ssl.SSLException: Received fatal alert: certificate_unknown

打开F12,重新连一下

SpringCloud Gateway--支持https_spring boot_06

解决方法​:

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

SpringCloud Gateway--支持https_.net_07

疑问及解答​ 

问:对于自己颁发的证书,为了使wss连接成功,每一个wss都要这样改为https然后访问一下吗?

答:不是的。只要访问了其对应的https、域名(ip)、端口的一个网址,同一https/wss+域名(ip)+端口和的网址/websocket连接全都可以正常了。比如:登录时用的是https,之后所有同一https/wss+域名(ip)+端口的网址/websocket连接就全部可以用了,而项目里一般websocket的域名(ip)+端口与接口(比如登录)是一样的,所以所有wss都正常连接了。

其他网址

比较老的gateway版本不支持将https转为http然后与微服务通信,解决方法见下方参考网址

​​spring-cloud-gateway使用https注意事项2---如何在转发后端服务的时候使用http - 简书​​


举报

相关推荐

0 条评论