通过服务端部署HTTPS与WSS证书配置
一、Spring Cloud Gateway配置HTTPS
1、阿里云申请免费证书
登陆之后,直接在产品和服务器中搜索【证书】
2、购买证书
3、配置域名
4、下载证书
这里下载的是tomcat证书
上面txt文件内容是密码
5、配置网关ssl
server:
port: 9527
#以下配置ssl证书
ssl:
key-store: classpath:7225224_xfjs.fun.pfx #证书文件路径
key-store-type: PKCS12 #证书类型
enabled: true
key-store-password: *******11123 #证书验证密码
- 把证书复制到resources目录下
- bootstrap.yml配置ssl,如上配置
6、如果出现如下错误
- 则要在pom.xml添加一个插件:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<encoding>UTF-8</encoding>
<!-- 过滤后缀为pkcs12、jks的证书文件 -->
<nonFilteredFileExtensions>
<nonFilteredFileExtension>pfx</nonFilteredFileExtension>
<nonFilteredFileExtension>jks</nonFilteredFileExtension>
</nonFilteredFileExtensions>
</configuration>
</plugin>
7、此时访问网关会报如下错误
io.netty.handler.codec.DecoderException: io.netty.handler.ssl.NotSslRecordException: not an SSL/TLS record: 485454502f312e3120343030200d0a5472616e736665722d456e636f64696e673a206368756e6b65640d0a446174653a205468752c2031372053657020323032302030383a32333a323320474d540d0a436f6e6e656374696f6e3a20636c6f73650d0a0d0a300d0a0d0a
at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:459)
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:265)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1434)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:965)
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163)
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:628)
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:563)
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:480)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:442)
at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:884)
at java.lang.Thread.run(Thread.java:748)
Caused by: io.netty.handler.ssl.NotSslRecordException: not an SSL/TLS record: 485454502f312e3120343030200d0a5472616e736665722d456e636f64696e673a206368756e6b65640d0a446174653a205468752c2031372053657020323032302030383a32333a323320474d540d0a436f6e6e656374696f6e3a20636c6f73650d0a0d0a300d0a0d0a
at io.netty.handler.ssl.SslHandler.decodeJdkCompatible(SslHandler.java:1178)
at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1243)
at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:489)
at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:428)
... 15 common frames omitted
- 是因为用户使用https访问网关,网关也会使用https访问各个微服务,解决方法是在bootstrap.yml配置把https转成http:
#这是资料的原版写法------------------------------
spring:
cloud:
gateway:
routes:
- id: router1
predicates:
- Host=sample.com
- Path=/**
uri: lb:http://sample-web
#这是我在项目中的实际写法-----------------------------------------------
spring:
application:
name: gateway-server
cloud:
nacos:
server-addr: 000.000.000.000:00#换成自己的服务地址
gateway:
discovery:
locator:
enabled: true #开启服务发现
lower-case-service-id: true #启用小驼峰的服务名称访问服务
routes:
- id: xf-admin_router
#uri: lb://xf-admin #转发到哪个目的地
#下面哪个为什么要加上 http: 呢,因为使用ssl证书之后,网关转发的也是https请求,但是调用的微服务需要的是http请求,所以这样
uri: lb:http://xf-admin #转发到哪个目的地
predicates:
- Path=/admin/**
- lb:[overwite scheme]😕/sample-web 这行加上http就行了,原理查看LoadBalancerClientFilter.class源码
8、配置tomcat(如果是使用jar包部署则不需要这一步)
- 解压已下载保存到本地的Tomcat证书文件。
- 解压后您将看到文件夹中有2个文件,您可为两个证书文件重命名。
- 证书文件(domain name.pfx):以.pfx为后缀或文件类型。
密码文件(pfx-password.txt):以.txt为后缀或文件类型。 - 在Tomcat安装目录下新建cert目录,将解压的证书和密码文件拷贝到cert目录下。
- 修改配置文件server.xml,并保存。
- 文件路径:Tomcat安装目录/conf/server.xml
- 在标签内添加:
<Connector port="443"
protocol="HTTP/1.1"
SSLEnabled="true"
scheme="https"
secure="true"
keystoreFile="D:/web-server/apache-tomcat-common/cert/domain.pfx"
keystoreType="PKCS12"
keystorePass="password"
clientAuth="false"
SSLProtocol="TLSv1+TLSv1.1+TLSv1.2"
ciphers="TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,TLS_RSA_WITH_AES_128_CBC_SHA256,TLS_RSA_WITH_AES_256_CBC_SHA256"/>
可选: 配置web.xml文件,开启HTTP强制跳转HTTPS。在文件后添加以下内容:
<login-config>
<!-- Authorization setting for SSL -->
<auth-method>CLIENT-CERT</auth-method>
<realm-name>Client Cert Users-only Area</realm-name>
</login-config>
<security-constraint>
<!-- Authorization setting for SSL -->
<web-resource-collection >
<web-resource-name >SSL</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
二、Gateway同时支持https和http请求
这个配置简单,但是我们需要同时支持http 和https
目前这个问题官方还没有正式解决
民间人士,想到的办法是 在启动一个Netty 监听另外一个端口 如:8080,这个端口是不带 s 的也就是 http
当访问8080时 跳转到配置https 的如8443端口
经过一番查找
-
https://github.com/spring-projects/spring-boot/issues/12035
-
https://stackoverflow.com/questions/49045670/spring-webflux-redirect-http-to-https/53000573#53000573
具体实现
package cn.com.test.gateway.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;
/**
* 该类主要是将http请求转化为https请求
* @author shkstart
* @create 2022-02-17 21:47
*/
@Configuration
public class HttpToHttpsRedirectConfig {
@Value("${server.http.port}")
private int httpPort;
@Value("${server.port}")
private int serverPort;
@PostConstruct
public void startRedirectServer() {
NettyReactiveWebServerFactory httpNettyReactiveWebServerFactory = new NettyReactiveWebServerFactory(httpPort);
httpNettyReactiveWebServerFactory.getWebServer((request, response) -> {
URI uri = request.getURI();
URI httpsUri;
try {
httpsUri = new URI("https", uri.getUserInfo(), uri.getHost(), serverPort, 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();
}
}
yml 配置文件配置:
#分为俩个端口号,server.port 是配置https 的端口, http.port 是监听的端口
server:
http:
port: 4321
port: 1234
ssl:
enabled: true
key-alias: scg
key-store: classpath:ssl/test.pfx
key-store-password: ******sad #换成自己的
keyStoreType: PKCS12
- 启动后访问,不带s 的4321端口会转发到1234端口。
三、Netty配置证书,实现wss访问
1、创建证书
自制证书:
keytool -genkey -keysize 2048 -validity 365 -keyalg RSA -dnam e "CN=gornix.com" -keypass 654321 -storepass 123456 -keystore gornix.jks
keytool为JDK提供的生成证书工具
- -keysize 2048 密钥长度2048位(这个长度的密钥目前可认为无法被暴力破解)
- -validity 365 证书有效期365天
- -keyalg RSA 使用RSA非对称加密算法
- -dname “CN=gornix.com” 设置Common Name为gornix.com,这是我的域名
- -keypass 654321 密钥的访问密码为654321
- -storepass 123456 密钥库的访问密码为123456(其实这两个密码也可以设置一样,通常都设置一样,方便记)
- -keystore gornix.jks 指定生成的密钥库文件为gornix.jks
2、保存证书文件(我这里直接放在resource目录下)
把生成的证书放到服务器上(也可以放在本地,不过这样要在本地代码配置ssl证书,使用https才可以,理论如此,因为我本地没有配ssl所以我直接部署在服务器上使用的),我放在/home/new 包下
3、jks证书处理工具类
package com.xf.chat.websocket;
import org.springframework.util.ResourceUtils;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.KeyStore;
/**
* 这个类主要是处理 wss证书
* @author shkstart
* @create 2022-02-10 22:41
*/
public class SslUtil {
private static volatile SSLContext SSL_CONTENT = null;
public static SSLContext createSSLContext(String type, String path, String password) throws Exception {
if (null == SSL_CONTENT) {
synchronized (SslUtil.class) {
if (null == SSL_CONTENT) {
//type应该是 JKS
KeyStore ks = KeyStore.getInstance(type); /// "JKS"
//读取resource下的文件
//File file = ResourceUtils.getFile("classpath:excleTemplate/test.xlsx");
File file = ResourceUtils.getFile(path);
InputStream ksInputStream = new FileInputStream(file);
//读取磁盘文件
//InputStream ksInputStream = new FileInputStream(path); /// 证书存放地址
ks.load(ksInputStream, password.toCharArray());
//KeyManagerFactory充当基于密钥内容源的密钥管理器的工厂。
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());//getDefaultAlgorithm:获取默认的 KeyManagerFactory 算法名称。
kmf.init(ks, password.toCharArray());
//SSLContext的实例表示安全套接字协议的实现,它充当用于安全套接字工厂或 SSLEngine 的工厂。
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), null, null);
SSL_CONTENT =sslContext;
}
}
}
return SSL_CONTENT;
}
}
4、初始化netty时加载证书类
package com.xf.chat.websocket;
import com.xf.chat.service.ChatMsgService;
import com.xf.chat.websocket.handler.ChatHandler;
import com.xf.chat.websocket.handler.QuitHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import org.springframework.beans.factory.annotation.Autowired;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
/**
* @author shkstart
* @create 2021-12-04 23:37
*/
public class WSServerInitialzer extends ChannelInitializer<NioSocketChannel> {
/**
* 初始化方法
*
* @param channel
* @throws Exception
*/
@Override
protected void initChannel(NioSocketChannel channel) throws Exception {
//获取管道
ChannelPipeline pipeline = channel.pipeline();
/**
* 这里配置自制的wss证书
* type: 这里用JKS
* path: 证书文件地址
* password: 证书密码
*/
String path = "classpath:gornix.jks";
String password = "123456";
SSLContext sslContext = SslUtil.createSSLContext("JKS", path, password);
SSLEngine sslEngine = sslContext.createSSLEngine();
// 是否使用客户端模式
// sslEngine.setNeedClientAuth(false);
//是否需要验证客户端
sslEngine.setUseClientMode(false); //服务器端模式
pipeline.addLast("ssl", new SslHandler(sslEngine));
//------------------------------------------------------------
//加入webSocket 基于http协议的编解码器
pipeline.addLast(new HttpServerCodec());
//对数据流写 提供支持
pipeline.addLast(new ChunkedWriteHandler());
//对httpMessage进行聚合处理,聚合成request或response
pipeline.addLast(new HttpObjectAggregator(1024 * 64));
/**
* 这个handler 可以帮住处理
* 握手动作:handshaking(close、ping、pong) 即心跳
* 对于webSocket来讲,都是以frams进行传输的,不同的数据类型独赢的frams也不同
*/
//访问路径这里得wss是后边得后缀wss,至于前边得wss是签名配置过wss证书 "wss://127.0.0.1:8000/wss",
pipeline.addLast(new WebSocketServerProtocolHandler("/wss"));
/**
* 自定义handle
*/
pipeline.addLast(new ChatHandler());
//处理连接断开的handler
pipeline.addLast(new QuitHandler());
}
}
使用nginx反向代理部署https和wss(推荐)
(必读)
通过这种方法部署之后可能还需要在gateway上部署一边证书,不需要部署netty证书,详细请参看一条
1、阿里云申请免费证书
登陆之后,直接在产品和服务器中搜索【证书】
2、购买证书
3、配置域名
4、下载证书
这里下载的是naginx证书
5、将下载下来的证书解压后的两个文件上传至服务器
- 在 /usr/local/nginx/conf下创建 cert文件夹用于存放证书文件
- 将解压缩后的两个证书文件上传cert文件夹下
6、修改conf下的nginx.conf配置文件
#user nobody;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
#如果$http_upgrade 不为 '' (空), 则$connection_upgrade 为 upgrade 。
#如果$http_upgrade 为 '' (空), 则 $connection_upgrade 为 close。
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
#表示的是 nginx负载均衡:
#两台服务器 (ip1:port1)和(ip2:port2) 。
#keepalive 1000 表示的是每个nginx进程中上游服务器保持的空闲连接,当空闲连接过多时,会关闭最少使用的空闲连接.当然,这不是限制连接总数的,可以想象成空闲连接池的大小,设置的值应该是上游服务器能够承受的。
upstream wsbackend {
server 00.00.000.000:8000 #换成自己的;
#server ip1:port1;
#server ip2:port2;
#keepalive 1000;
}
#以下属性中,以ssl开头的属性表示与证书配置有关。
server {
listen 443 ssl;
#配置HTTPS的默认访问端口为443。
#如果未在此处配置HTTPS的默认访问端口,可能会造成Nginx无法启动。
#如果您使用Nginx 1.15.0及以上版本,请使用listen 443 ssl代替listen 443和ssl on。
server_name xfjs.fun; #需要将yourdomain替换成证书绑定的域名。
root html;
index index.html index.htm;
ssl_certificate cert/7225224_xfjs.fun.pem; #需要将cert-file-name.pem替换成已上传的证书文件的名称。
ssl_certificate_key cert/7225224_xfjs.fun.key; #需要将cert-file-name.key替换成已上传的证书私钥文件的名称。
ssl_session_timeout 5m;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
#表示使用的加密套件的类型。
ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3; #表示使用的TLS协议的类型。
ssl_prefer_server_ciphers on;
#这里配置的是wss相关配置
#listen 20038 表示 nginx 监听的端口
#locations / 表示监听的路径(/表示所有路径,通用匹配,相当于default)
#proxt_http_version 1.1 表示反向代理发送的HTTP协议的版本是1.1,HTTP1.1支持长连接
#proxy_pass http://wsbackend; 表示反向代理的uri,这里可以使用负载均衡变量
#proxy_redirect off; 表示不要替换路径,其实这里如果是/则有没有都没关系,因为default也是将路径替换到proxy_pass的后边
#proxy_set_header Host $host; 表示传递时请求头不变, $host是nginx内置变量,表示的是当前的请求头,proxy_set_header表示设置请求头
#proxy_set_header X-Real-IP $remote_addr; 表示传递时来源的ip还是现在的客户端的ip
#proxy_read_timeout 3600s; 表的两次请求之间的间隔超过 3600s 后才关闭这个连接,默认的60s,自动关闭的元凶
#proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 表示X-Forwarded-For头不发生改变
#proxy_set_header Upgrade $http_upgrade; 表示设置Upgrade不变
#proxy_set_header Connection $connection_upgrade; 表示如果 $http_upgrade为upgrade,则请求为upgrade(websocket),如果不是,就关闭连接
#此时,访问 wss://域名:443/wss 就会被转发到 ip1:port1 和 ip2:port2 上。
location /wss {
proxy_http_version 1.1;
proxy_pass http://wsbackend;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 3600s;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
#以下配置的就是通过https访问443端口之后要跳转的网站首页
location / {
root /app/fighting-admin/dist;
index index.html index.htm;
try_files $uri $uri/ @router;
index index.html;
}
location @router {
rewrite ^.*$ /index.html last;
}
}
#这里的配置是将所有的80端口的请求重定向到https请求
server {
listen 80;
server_name xfjs.fun; #需要将yourdomain替换成证书绑定的域名。
rewrite ^(.*)$ https://$host$1; #将所有HTTP请求通过rewrite指令重定向到HTTPS。
location / {
index index.html index.htm;
}
}
}