本篇内容主要讲解“Netty与Qt如何实现wss双向认证”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Netty与Qt如何实现wss双向认证”吧!
让客户满意是我们工作的目标,不断超越客户的期望值来自于我们对这个行业的热爱。我们立志把好的技术通过有效、简单的方式提供给客户,将通过不懈努力成为客户在信息化领域值得信任、有价值的长期合作伙伴,公司提供的服务项目有:域名申请、虚拟空间、营销软件、网站建设、施秉网站维护、网站推广。
Netty 4.1.42.Final + Qt 5.9 + SpringBoot2.1.8
要使用ssl双向验证,就必须先要生成服务端和客户端的证书,并相互添加信任,具体流程如下(本人调试这个用例的时候,花了很多时间来验证证书是否正确,以及握手失败的原因,这里证书生成过程只要按流程走,本人能保证绝对没有问题) 现在打开cmd,在哪个目录下打开,证书就会放在哪个目录下:
keytool -genkey -alias securechat -keysize 2048 -validity 365 -keyalg RSA -dname "CN=localhost" -keypass sNetty -storepass sNetty -keystore sChat.jks -deststoretype pkcs12
-keysize 2048 密钥长度2048位(这个长度的密钥目前可认为无法被暴力破解)
-validity 365 证书有效期365天
-keyalg RSA 使用RSA非对称加密算法
-dname "CN=localhost" 设置Common Name为localhost
-keypass sNetty密钥的访问密码为sNetty
-storepass sNetty密钥库的访问密码为sNetty(其实这两个密码也可以设置一样,通常都设置一样,方便记)
-keystore sChat.jks 指定生成的密钥库文件为sChata.jks
-deststoretype pkcs12 必须要pkcs12
keytool -export -alias securechat -keystore sChat.jks -storepass sNetty -file sChat.cer
keytool -genkey -alias smcc -keysize 2048 -validity 365 -keyalg RSA -dname "CN=localhost" -keypass sNetty -storepass sNetty -keystore cChat.jks -deststoretype pkcs12
keytool -import -trustcacerts -alias securechat -file sChat.cer -storepass sNetty -keystore cChat.jks
keytool -export -alias smcc -keystore cChat.jks -storepass sNetty -file cChat.cer
keytool -import -trustcacerts -alias smcc -file cChat.cer -storepass sNetty -keystore sChat.jks
到这里,证书就生成完毕了,我们就可以得到两个jks文件,一个是服务端的sChat.jks ,一个是客户端的cChat.jks ,这两个文件后面初始化sslCOntext的时候会用到
package com.cccc.ocpptest; import java.io.FileInputStream; import java.io.IOException; import java.security.KeyStore; import java.security.NoSuchAlgorithmException; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; /** * ContextSSLFactory * * @author MFY * @date 2019/10/1 */ public class ContextSSLFactory { private static final SSLContext SSL_CONTEXT_S ; private static final SSLContext SSL_CONTEXT_C ; static{ SSLContext sslContext = null ; SSLContext sslContext2 = null ; try { sslContext = SSLContext.getInstance("TLSv1") ; sslContext2 = SSLContext.getInstance("TLSv1") ; } catch (NoSuchAlgorithmException e1) { e1.printStackTrace(); } try{ if(getKeyManagersServer() != null && getTrustManagersServer() != null ){ sslContext.init(getKeyManagersServer(), getTrustManagersServer(), null); } if(getKeyManagersClient() != null && getTrustManagersClient() != null){ sslContext2.init(getKeyManagersClient(), getTrustManagersClient(), null); } }catch(Exception e){ e.printStackTrace() ; } sslContext.createSSLEngine().getSupportedCipherSuites() ; sslContext2.createSSLEngine().getSupportedCipherSuites() ; SSL_CONTEXT_S = sslContext ; SSL_CONTEXT_C = sslContext2 ; } public ContextSSLFactory(){ } public static SSLContext getSslContext(){ return SSL_CONTEXT_S ; } public static SSLContext getSslContext2(){ return SSL_CONTEXT_C ; } private static TrustManager[] getTrustManagersServer(){ FileInputStream is = null ; KeyStore ks = null ; TrustManagerFactory keyFac = null ; TrustManager[] kms = null ; try { // 获得KeyManagerFactory对象. 初始化位默认算法 keyFac = TrustManagerFactory.getInstance("SunX509") ; is =new FileInputStream( "D:\\work\\JavaProjects\\ocpptest\\keys\\sChat.jks" ); ks = KeyStore.getInstance("JKS") ; String keyStorePass = "sNetty" ; ks.load(is , keyStorePass.toCharArray()) ; keyFac.init(ks) ; kms = keyFac.getTrustManagers() ; } catch (Exception e) { e.printStackTrace(); } finally{ if(is != null ){ try { is.close() ; } catch (IOException e) { e.printStackTrace(); } } } return kms ; } private static TrustManager[] getTrustManagersClient(){ FileInputStream is = null ; KeyStore ks = null ; TrustManagerFactory keyFac = null ; TrustManager[] kms = null ; try { // 获得KeyManagerFactory对象. 初始化位默认算法 keyFac = TrustManagerFactory.getInstance("SunX509") ; is =new FileInputStream( "D:\\work\\JavaProjects\\ocpptest\\keys\\cChat.jks" ); ks = KeyStore.getInstance("JKS") ; String keyStorePass = "sNetty" ; ks.load(is , keyStorePass.toCharArray()) ; keyFac.init(ks) ; kms = keyFac.getTrustManagers() ; } catch (Exception e) { e.printStackTrace(); } finally{ if(is != null ){ try { is.close() ; } catch (IOException e) { e.printStackTrace(); } } } return kms ; } private static KeyManager[] getKeyManagersServer(){ FileInputStream is = null ; KeyStore ks = null ; KeyManagerFactory keyFac = null ; KeyManager[] kms = null ; try { // 获得KeyManagerFactory对象. 初始化位默认算法 keyFac = KeyManagerFactory.getInstance("SunX509") ; is =new FileInputStream( "D:\\work\\JavaProjects\\ocpptest\\keys\\sChat.jks" ); ks = KeyStore.getInstance("JKS") ; String keyStorePass = "sNetty" ; ks.load(is , keyStorePass.toCharArray()) ; keyFac.init(ks, keyStorePass.toCharArray()) ; kms = keyFac.getKeyManagers() ; } catch (Exception e) { e.printStackTrace(); } finally{ if(is != null ){ try { is.close() ; } catch (IOException e) { e.printStackTrace(); } } } return kms ; } private static KeyManager[] getKeyManagersClient(){ FileInputStream is = null ; KeyStore ks = null ; KeyManagerFactory keyFac = null ; KeyManager[] kms = null ; try { // 获得KeyManagerFactory对象. 初始化位默认算法 keyFac = KeyManagerFactory.getInstance("SunX509") ; is =new FileInputStream("D:\\work\\JavaProjects\\ocpptest\\keys\\cChat.jks"); ks = KeyStore.getInstance("JKS") ; String keyStorePass = "sNetty" ; ks.load(is , keyStorePass.toCharArray()) ; keyFac.init(ks, keyStorePass.toCharArray()) ; kms = keyFac.getKeyManagers() ; } catch (Exception e) { e.printStackTrace(); } finally{ if(is != null ){ try { is.close() ; } catch (IOException e) { e.printStackTrace(); } } } return kms ; } }
package com.cccc.ocpptest; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.handler.codec.http.FullHttpRequest; import org.eclipse.jetty.util.MultiMap; import org.eclipse.jetty.util.UrlEncoded; @ChannelHandler.Sharable public class CustomUrlHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { // 只针对FullHttpRequest类型的做处理,其它类型的自动放过 if (msg instanceof FullHttpRequest) { FullHttpRequest request = (FullHttpRequest) msg; String uri = request.uri(); int idx = uri.indexOf("?"); if (idx > 0) { String query = uri.substring(idx + 1); // uri中参数的解析使用的是jetty-util包,其性能比自定义及正则性能高。 MultiMapvalues = new MultiMap (); UrlEncoded.decodeTo(query, values, "UTF-8"); request.setUri(uri.substring(0, idx)); } } ctx.fireChannelRead(msg); } }
package com.cccc.ocpptest; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; import org.slf4j.Logger; import org.slf4j.LoggerFactory; //import java.net.InetAddress; @ChannelHandler.Sharable public class MyWebSocketHandler extends ChannelInboundHandlerAdapter { private static final Logger logger = LoggerFactory.getLogger(MyWebSocketHandler.class); @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { logger.info("与客户端建立连接,通道开启"); } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { logger.info("与客户端断开连接,通道关闭"); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { TextWebSocketFrame message = (TextWebSocketFrame) msg; logger.info("服务端收到数据: " + message.text()); // 此处需注意返回的数据的格式为TextWebSocketFrame。否则客户端收不到消息 ctx.channel().writeAndFlush(new TextWebSocketFrame(message.text())); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { logger.error(cause.getMessage()); // cause.printStackTrace(); } }
package com.cccc.ocpptest; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.net.ssl.SSLEngine; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.ChannelPipeline; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; 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.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; import io.netty.handler.ssl.SslHandler; import io.netty.handler.stream.ChunkedWriteHandler; import org.springframework.stereotype.Component; /** * NettySocketServer * * @author MFY * @date 2019/10/1 */ @Component public class NettySocketServer { private static SslHandler sslHandler = null ; private EventLoopGroup bossGroup = null ; private EventLoopGroup workerGroup = null ; @PostConstruct public void start(){ bossGroup = new NioEventLoopGroup() ; workerGroup = new NioEventLoopGroup() ; try{ ServerBootstrap serverStrap = new ServerBootstrap() ; serverStrap.group(bossGroup , workerGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 128) .option(ChannelOption.SO_KEEPALIVE, true) .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 1000 * 5 * 60) .handler(new LoggingHandler(LogLevel.DEBUG)) .childHandler(new ChannelInitializer() { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { ChannelPipeline pie = socketChannel.pipeline() ; pie.addLast(new HttpServerCodec()); pie.addLast(new HttpObjectAggregator(65535)); pie.addLast(new ChunkedWriteHandler()); // 对websocket url中的参数做解析处理的Handler pie.addLast(new CustomUrlHandler()); // 对websocket做支持,其路径为/ws pie.addLast(new WebSocketServerProtocolHandler("/ws")); // 自定义业务逻辑处理的Handler pie.addLast(new MyWebSocketHandler()); SSLEngine engine = ContextSSLFactory.getSslContext().createSSLEngine(); engine.setUseClientMode(false); engine.setNeedClientAuth(true); pie.addFirst("ssl", new SslHandler(engine)); } }); serverStrap.bind(30021).sync() ; System.out.println("服务已开启"); }catch(Exception e){ e.printStackTrace() ; bossGroup.shutdownGracefully() ; workerGroup.shutdownGracefully() ; } } private SslHandler getSslHandler(){ if(sslHandler == null ){ SSLEngine sslEngine = ContextSSLFactory.getSslContext().createSSLEngine() ; sslEngine.setUseClientMode(false) ; //false为单向认证,true为双向认证 sslEngine.setNeedClientAuth(true) ; sslHandler = new SslHandler(sslEngine); } return sslHandler ; } @PreDestroy public void close(){ bossGroup.shutdownGracefully() ; workerGroup.shutdownGracefully() ; } // public static void main(String[] args) { // new NettySocketServer().start() ; // } }
package com.cccc.ocpptest; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class OcpptestApplication { public static void main(String[] args) { SpringApplication.run(OcpptestApplication.class, args); } }
三、Qt Wss测试
#include#include #include #include #include #include #include #include int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); QWebSocket webSocket; QObject::connect(&webSocket, &QWebSocket::connected, [](){ qDebug() << "connected"; }); QObject::connect(&webSocket, &QWebSocket::disconnected, [](){ qDebug() << "disconnected"; }); QObject::connect(&webSocket,(void(QWebSocket::*)(QAbstractSocket::SocketError)) &QWebSocket::error, [&webSocket](QAbstractSocket::SocketError){ qDebug() << webSocket.errorString(); }); QFile cerFile1("D:/work/JavaProjects/ocpptest/keys/cChat.jks"); if (cerFile1.open(QFile::ReadOnly)) { QSslKey sslKey; QSslCertificate sslCer; QList caCers; if (QSslCertificate::importPkcs12(&cerFile1, &sslKey, &sslCer, &caCers, "sNetty")) { qDebug() << "OK"; QSslConfiguration sslConfig; sslConfig.setCaCertificates(caCers); sslConfig.setPrivateKey(sslKey); sslConfig.setLocalCertificate(sslCer); sslConfig.setProtocol(QSsl::AnyProtocol); webSocket.setSslConfiguration(sslConfig); QUrl url("wss://localhost:8000/ws"); QNetworkRequest request(url); webSocket.open(request); } cerFile1.close(); } return a.exec(); }
到此,相信大家对“Netty与Qt如何实现wss双向认证”有了更深的了解,不妨来实际操作一番吧!这里是创新互联网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!