基于Netty的MQTT Server實(shí)現(xiàn)并支持SSL

參考上文,上文完成了 MQTT Server 的基本功能,但是還差了 SSL 的功能。

一. 添加過程

以 Netty 為基礎(chǔ)加上 SSL 功能比較簡單,幾行代碼就可以

// 創(chuàng)建SSL上下文
if (option.isSsl()) {
    sslContext = SslContextBuilder.forServer(
                       new File(option.getServerCertFile()),
                       new File(option.getKeyFile()))
         .trustManager(new File(option.getCaCertFile())).build();
}
......

if(sslContext!=null) {
       // 將SSL上下文添加到ChannelPipeline中
        channelPipeline.addLast(sslContext.newHandler(ch.alloc()));
}

需要證書文件的提供,包括3個(gè)文件

  /**
     * CA證書文件,一般叫ca.crt
     * 指令參考:openssl req -new -x509 -keyout ca.key -out ca.crt -days 36500
     */
    private String caCertFile;

    /**
     * an X.509 certificate chain file in PEM format
     * server證書文件,一般叫server.crt
     * 指令參考:openssl x509 -req -days 36500 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt
     * ,如果需要和域名或ip綁定需要加上 -extfile <(printf "subjectAltName=IP:127.0.0.1")
     */
    private String serverCertFile;
    /**
     * a PKCS#8 private key file in PEM format
     * 私鑰文件,因?yàn)閚etty只支持pkcs8,所以需要2個(gè)步驟,第二個(gè)步驟改成pkcs8格式
     * openssl genrsa -des3 -out server.key 1024
     * openssl pkcs8 -topk8 -in server.key -out pkcs8_server.key -nocrypt
     */
    private String keyFile;

二. 證書創(chuàng)建

主要參考 https://www.cnblogs.com/exmyth/p/14808872.html
1.生成ca證書

openssl req -new -x509 -keyout ca.key -out ca.crt -days 36500

在本目錄得到 ca.key 和 ca.crt 文件

2.生成服務(wù)端和客戶端私鑰

openssl genrsa -des3 -out server.key 1024
openssl genrsa -des3 -out client.key 1024

3.根據(jù) key 生成 csr 文件

openssl req -new -key server.key -out server.csr
openssl req -new -key client.key -out client.csr

4.根據(jù) ca 證書 server.csr 和 client.csr 生成 x509 證書

openssl x509 -req -days 36500 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt
openssl x509 -req -days 36500 -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt

需要和ip綁定則加上參數(shù),我都是在本地測試,所以設(shè)置ip為127.0.0.1

openssl x509 -req -days 36500 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -extfile <(printf "subjectAltName=IP:127.0.0.1")
openssl x509 -req -days 36500 -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt -extfile <(printf "subjectAltName=IP:127.0.0.1")

5.將 key 文件進(jìn)行 PKCS#8 編碼

openssl pkcs8 -topk8 -in server.key -out pkcs8_server.key -nocrypt
openssl pkcs8 -topk8 -in client.key -out pkcs8_client.key -nocrypt

最后得到有用的文件分別為

服務(wù)器端: ca.crt、server.crt、pkcs8_server.key
客戶端端: ca.crt、client.crt、pkcs8_client.key

我這里生成了一套在 cert 目錄下,大家可以測試使用。

三. 碰到的問題

因?yàn)橐粋€(gè)老的 bug 導(dǎo)致一致沒有調(diào)通。

 /**
     * 服務(wù)端 當(dāng)讀超時(shí)時(shí) 會(huì)調(diào)用這個(gè)方法
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        super.userEventTriggered(ctx, evt);
        logger.info("MQTT Server Channel timeout:{}", ctx.channel().id().asLongText());
        ctx.close();
    }

這個(gè)地方有一個(gè)錯(cuò)誤,userEventTriggered 并不只是超時(shí)的時(shí)候才會(huì)觸發(fā),以前在很多項(xiàng)目下沒有引起問題是因?yàn)橐郧耙恢睕]有用到 SSL ,加上 SSL 就導(dǎo)致錯(cuò)誤了,因?yàn)?SSL 會(huì)在連接的時(shí)候會(huì)觸發(fā)這個(gè)事件函數(shù),但是我強(qiáng)行關(guān)閉連接了,導(dǎo)致一直有問題。最后改成以下代碼就可以了:

/**
     * 服務(wù)端 當(dāng)讀超時(shí)時(shí) 會(huì)調(diào)用這個(gè)方法,SSL的時(shí)候也會(huì)觸發(fā)SslCloseCompletionEvent,SslHandshakeCompletionEvent
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        super.userEventTriggered(ctx, evt);
        logger.info("MQTT Server Channel Event:{},{}", ctx.channel().id().asLongText(), evt.toString());
        if (evt instanceof IdleStateEvent) {
            ctx.close();
        }
    }

四. 測試

用了3種方式來測試

  1. 利用客戶端工具 MQTTX


    微信圖片_20230818173235.png

    配置如上圖,證書其中 key 文件選擇的是 pkcs8 格式的,因?yàn)閜kcs12格式的文件有密碼,但是這個(gè)工具沒有找到設(shè)置密碼的地方。

  2. 利用Netty實(shí)現(xiàn) java 的 mqtt 客戶端,主要設(shè)置 SSL 的代碼就二句代碼

 final SslContext sslCtx = SslContextBuilder.forClient()
                //雙向驗(yàn)證
                .keyManager(new File("D:\\Work\\framework\\mqttserver-duoxian\\cert\\client.crt"),
                        new File("D:\\Work\\framework\\mqttserver-duoxian\\cert\\pkcs8_client.key"))
                .trustManager(new File("D:\\Work\\framework\\mqttserver-duoxian\\cert\\ca.crt"))// CA證書,驗(yàn)證對方證書

//                 不驗(yàn)證SERVER
//                 .trustManager(InsecureTrustManagerFactory.INSTANCE)
                .build();
......

ch.pipeline().addLast(sslCtx.newHandler(ch.alloc()));

基本和服務(wù)端的用戶類似,如果不用雙向認(rèn)證就只需要用到 ca.crt 。

注意 netty 只支持 pkcs8 格式的密鑰文件。

這個(gè)測試類叫 MqttSslClientNetty.java 大家可以在 git 的 src 下目錄找到。

  1. 利用 eclipse.paho 的 mqtt 庫
    這個(gè)也是 MQTT 的常用庫,寫法比 netty 復(fù)雜一些,主要代碼如下:
 // 加載密鑰文件和證書文件
String privateKeyFile = "D:\\Work\\framework\\mqttserver-duoxian\\cert\\client.key";
String certificateFile = "D:\\Work\\framework\\mqttserver-duoxian\\cert\\client.crt";
String caficateFile = "D:\\Work\\framework\\mqttserver-duoxian\\cert\\ca.crt";
//雙向認(rèn)證
//SSLSocketFactory socketFactory = getSocketFactory(caficateFile, certificateFile, privateKeyFile, "123456");
 //單向認(rèn)證
SSLSocketFactory socketFactory = getSocketFactory(caficateFile);

同樣單向認(rèn)證只需要用到 ca 證書。那個(gè) getSocketFactory 函數(shù)有2個(gè)實(shí)現(xiàn),分別對應(yīng)單向驗(yàn)證和雙向驗(yàn)證。主要是用到了 org.bouncycastle.openssl 庫。

注意這里必須用 pkcs12 格式的密鑰文件。

這個(gè)測試類叫 MqttSslClientPaho.java 大家可以在 git 的 src 下目錄找到。

所有代碼都已經(jīng)提交到 git

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容