From 716b51693a6b10a6a843b0f8158902e284d3435d Mon Sep 17 00:00:00 2001 From: 18201612589 <2746733890@qq.com> Date: Mon, 20 Dec 2021 09:00:11 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8A=A0=E5=AF=86=E6=9A=82=E6=9C=AA=E5=BC=80?= =?UTF-8?q?=E6=94=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/muyu/common/Config.java | 5 ++ .../muyu/netty/client/NettyClientInit.java | 68 +++++++++------- .../muyu/netty/operate/NettyClientMsg.java | 1 - .../com/muyu/netty/ssl/SslContextFactory.java | 73 ++++++++++++++++++ src/main/resources/ssl/cVehicleChat.cer | Bin 0 -> 715 bytes src/main/resources/ssl/cVehicleChat.jks | Bin 0 -> 3227 bytes src/main/resources/ssl/sVehicleChata.cer | Bin 0 -> 715 bytes src/main/resources/ssl/sVehicleChata.jks | Bin 0 -> 3239 bytes 8 files changed, 118 insertions(+), 29 deletions(-) create mode 100644 src/main/java/com/muyu/netty/ssl/SslContextFactory.java create mode 100644 src/main/resources/ssl/cVehicleChat.cer create mode 100644 src/main/resources/ssl/cVehicleChat.jks create mode 100644 src/main/resources/ssl/sVehicleChata.cer create mode 100644 src/main/resources/ssl/sVehicleChata.jks diff --git a/src/main/java/com/muyu/common/Config.java b/src/main/java/com/muyu/common/Config.java index 850a6d7..351046e 100644 --- a/src/main/java/com/muyu/common/Config.java +++ b/src/main/java/com/muyu/common/Config.java @@ -30,6 +30,11 @@ public class Config { */ public static ChannelHandlerContext ctx; + /** + * 加密方式 + */ + public static final String[] CIPHER_ARRAY = {"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256"}; + /** * 车辆VIN */ diff --git a/src/main/java/com/muyu/netty/client/NettyClientInit.java b/src/main/java/com/muyu/netty/client/NettyClientInit.java index 2040721..8569ec3 100644 --- a/src/main/java/com/muyu/netty/client/NettyClientInit.java +++ b/src/main/java/com/muyu/netty/client/NettyClientInit.java @@ -4,6 +4,7 @@ package com.muyu.netty.client; import com.muyu.common.Common; import com.muyu.common.Config; import com.muyu.netty.bean.NettyClientBean; +import com.muyu.netty.ssl.SslContextFactory; import io.netty.bootstrap.Bootstrap; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFuture; @@ -15,10 +16,14 @@ import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.DelimiterBasedFrameDecoder; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; +import io.netty.handler.ssl.SslHandler; import io.netty.handler.timeout.IdleStateHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; + /** * @author 牧鱼 @@ -45,40 +50,47 @@ public class NettyClientInit { try { Bootstrap b = new Bootstrap(); mClientHandler = new NettyClientHandler(); - b.group(Config.workerGroup).channel(NioSocketChannel.class) - // KeepAlive - .option(ChannelOption.SO_KEEPALIVE, true) - // Handler - .handler(new ChannelInitializer() { - - @Override - protected void initChannel(SocketChannel channel) throws Exception { + b.group(Config.workerGroup); + b.channel(NioSocketChannel.class); + b.option(ChannelOption.SO_KEEPALIVE, true); + b.handler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel channel) throws Exception { +// SSLContext sslCtx = SslContextFactory.getServerContext(); +// SSLEngine sslEngine = sslCtx.createSSLEngine(); + //设置加密套件 +// sslEngine.setEnabledCipherSuites(Config.CIPHER_ARRAY); +// sslEngine.setUseClientMode(false); +// sslEngine.setNeedClientAuth(true); +// channel.pipeline().addFirst("SslEstablish", new SslHandler(sslEngine)); // SSLEngine sslEngine = sslContext.createSSLEngine(); // sslEngine.setUseClientMode(false); //服务器端模式 // sslEngine.setNeedClientAuth(false); //不需要验证客户端 // channel.pipeline().addFirst("ssl", new SslHandler(sslEngine)); - //分包器 - channel.pipeline().addLast( - new DelimiterBasedFrameDecoder( - 1024, - Unpooled.copiedBuffer(Config.DATA_PACK_SEPARATOR.getBytes() - ) - ) - ); - // 心跳 - channel.pipeline().addLast("HBeat", new IdleStateHandler( - 20, - 10, 0)); - //编码器 - channel.pipeline().addLast("encoder", new StringEncoder()); - //解码器 - channel.pipeline().addLast("decoder", new StringDecoder()); + //分包器 + channel.pipeline().addLast( + new DelimiterBasedFrameDecoder( + 1024, + Unpooled.copiedBuffer(Config.DATA_PACK_SEPARATOR.getBytes() + ) + ) + ); + // 心跳 + channel.pipeline().addLast("HBeat", new IdleStateHandler( + 20, + 10, 0)); + //编码器 + channel.pipeline().addLast("encoder", new StringEncoder()); + //解码器 + channel.pipeline().addLast("decoder", new StringDecoder()); - //处理器 - channel.pipeline().addLast(mClientHandler); - } - }); + //处理器 + channel.pipeline().addLast(mClientHandler); + } + }); + // KeepAlive + // Handler future = b.connect(nettyClientBean.getHost(), nettyClientBean.getPort()).sync(); if (future.isSuccess()) { log.info("Client,链接服务端成功"); diff --git a/src/main/java/com/muyu/netty/operate/NettyClientMsg.java b/src/main/java/com/muyu/netty/operate/NettyClientMsg.java index 03005d5..0eeee67 100644 --- a/src/main/java/com/muyu/netty/operate/NettyClientMsg.java +++ b/src/main/java/com/muyu/netty/operate/NettyClientMsg.java @@ -43,7 +43,6 @@ public class NettyClientMsg { * 销毁netty */ public static void destroy(){ - log.info("发送断开连接消息:"+Config.NETTY_CLOSE); sendMsg(Config.NETTY_WILL_CLOSE + Config.VIN); Config.ctx = null; } diff --git a/src/main/java/com/muyu/netty/ssl/SslContextFactory.java b/src/main/java/com/muyu/netty/ssl/SslContextFactory.java new file mode 100644 index 0000000..f49b7c2 --- /dev/null +++ b/src/main/java/com/muyu/netty/ssl/SslContextFactory.java @@ -0,0 +1,73 @@ +package com.muyu.netty.ssl; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.KeyStore; + +public class SslContextFactory { + + private static final Logger log = LoggerFactory.getLogger(SslContextFactory.class); + + private static final String PROTOCOL = "TLS"; + + private static volatile SSLContext SERVER_CONTEXT = null; + + private static final String DEFAULT_PROPERTIES = "application.properties"; + + private static final String SSL_KEY_STORE_TYPE = "JKS"; + + private static final String SSL_KEY_STORE_PASSWORD = "vehicle"; + + private static final String SSL_KEY_STORE = System.getProperty("user.dir")+ File.separator + "src" +File.separator + "main" + File.separator + + "resources" + File.separator+"ssl"+File.separator+"cVehicleChat.jks"; + + private static void init(){ + InputStream keyStore = null; + InputStream trustStore = null; + try { + //初始化keyManagerFactory + KeyStore ks = KeyStore.getInstance(SSL_KEY_STORE_TYPE); + keyStore = new FileInputStream(SSL_KEY_STORE); + ks.load(keyStore, SSL_KEY_STORE_PASSWORD.toCharArray()); + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmf.init(ks, SSL_KEY_STORE_PASSWORD.toCharArray()); + //初始化TrustManagerFacotry + KeyStore ts = KeyStore.getInstance(SSL_KEY_STORE_TYPE); + trustStore = new FileInputStream(SSL_KEY_STORE); + ts.load(trustStore, SSL_KEY_STORE_PASSWORD.toCharArray()); + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(ts); + //生成SSLContext + SERVER_CONTEXT = SSLContext.getInstance(PROTOCOL); + SERVER_CONTEXT.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); + } catch (IOException e) { + throw new RuntimeException(e.getMessage(), e); + } catch (Exception e) { + throw new RuntimeException(e.getMessage(), e); + } finally { + try { + if (null != keyStore) { + keyStore.close(); + } + if (null != trustStore) { + trustStore.close(); + } + } catch (IOException e) { } + } + } + + static { + init(); + } + public static SSLContext getServerContext() { + return SERVER_CONTEXT; + } +} diff --git a/src/main/resources/ssl/cVehicleChat.cer b/src/main/resources/ssl/cVehicleChat.cer new file mode 100644 index 0000000000000000000000000000000000000000..c108c4aa4f5bce051724bbb556fc33fdc8399b8b GIT binary patch literal 715 zcmXqLVmfZn#JGL|GZP~d6N~!u`dtRRY@Awc9&O)w85y}*84N@Wg$xAPm_u2Zg*kKb zlM{0?@{3ChEMxqk4$`2{(z2;jZ z+akcJb5d-nWEXja^cT@N0PtnT;?b@@4a&BPuHGx)t*VBexHA6CucHRd)}{{ zuy*U$Q(TwVGcz$WGB7SyG>|ut1%|jRABz}^i0|EXckfL8@G`L>|7nf&ZSNE5qCb%X z5g13nKxAZ)Pkx!sR4#b;;OWCmB57QCdO}}j)<3pXUnckYO6L}q6#Kn%c1LWPy++?E z&16+&_^QH7g6j%CoL?oPd^2rj`1FsWmlNWuYS^Z=B%hz-@p6mS+%wjPtmFD0>B#q9 z`*G3k=cQWpzEgTjf8I*T?fIN)v+-ou@}-A@m=5`i%DMeiSQ}-3JTbVhm-&Ql+R^A7 z=@8yuk9isoWEEcUJGgf{i}0!nKWnS$F;6G*{4h$oA?vzdsdx9U-CNGId^5}0@wb0V z>c&`A38mk!o<-R!A3J1Ye!lO2KFD+$qbkUr# W{K1EaEoaKUn}?hXd)4f*zXSjeF)arG literal 0 HcmV?d00001 diff --git a/src/main/resources/ssl/cVehicleChat.jks b/src/main/resources/ssl/cVehicleChat.jks new file mode 100644 index 0000000000000000000000000000000000000000..4ee669d160caecc84a0da17488578d7ebcbf53c1 GIT binary patch literal 3227 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 Vr3X6gz6I+mf2ccurY|<*F95Y*CPe@M literal 0 HcmV?d00001 diff --git a/src/main/resources/ssl/sVehicleChata.jks b/src/main/resources/ssl/sVehicleChata.jks new file mode 100644 index 0000000000000000000000000000000000000000..45928000807be5fc9d385aa14f25e986e26c7d9e GIT binary patch literal 3239 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