Netty与Qt如何实现wss双向认证

本篇内容主要讲解“Netty与Qt如何实现wss双向认证”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Netty与Qt如何实现wss双向认证”吧!

让客户满意是我们工作的目标,不断超越客户的期望值来自于我们对这个行业的热爱。我们立志把好的技术通过有效、简单的方式提供给客户,将通过不懈努力成为客户在信息化领域值得信任、有价值的长期合作伙伴,公司提供的服务项目有:域名申请、虚拟空间、营销软件、网站建设、施秉网站维护、网站推广。

Netty 4.1.42.Final + Qt 5.9 + SpringBoot2.1.8

一、证书准备

要使用ssl双向验证,就必须先要生成服务端和客户端的证书,并相互添加信任,具体流程如下(本人调试这个用例的时候,花了很多时间来验证证书是否正确,以及握手失败的原因,这里证书生成过程只要按流程走,本人能保证绝对没有问题) 现在打开cmd,在哪个目录下打开,证书就会放在哪个目录下:

第一步:生成Netty服务端私钥和证书仓库命令

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

第二步:生成Netty服务端自签名证书

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

第四步:将Netty服务端证书导入到客户端的证书仓库中

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的时候会用到

二、Netty服务端

ContextSSLFactory.java

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

CustomUrlHandler.java

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包,其性能比自定义及正则性能高。
                MultiMap values = new MultiMap();
                UrlEncoded.decodeTo(query, values, "UTF-8");
                request.setUri(uri.substring(0, idx));
            }
        }
        ctx.fireChannelRead(msg);
    }
}

MyWebSocketHandler.java

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

NettySocketServer.java

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

}

OcpptestApplication.java

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测试

main.cpp

#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双向认证”有了更深的了解,不妨来实际操作一番吧!这里是创新互联网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!


分享标题:Netty与Qt如何实现wss双向认证
本文地址:http://pwwzsj.com/article/gohjsd.html