砍材农夫砍材农夫
  • 微信记账小程序
  • java
  • redis
  • mysql
  • 场景类
  • 框架类
  • vuepress搭建
  • hexo搭建
  • 云图
  • llm wiki

    • 基于karpathy
    • gradle
  • 常用工具

    • git
    • gradle
    • Zadig
    • it-tools
    • 开源推荐
    • curl
  • 大前端

    • nodejs
    • npm
    • webpack
    • 微信
    • 正则
    • uniapp
    • app
  • java

    • java基础
    • jdk体系
    • jvm
    • spring
    • spring_cloud
    • spring_boot
    • 分库分表
    • zookeeper
  • python

    • python基础
    • python高级
    • python框架
  • 算法

    • 算法
  • 网关

    • spring_cloud_gateway
    • openresty
  • 高可用

    • 秒杀
    • 分布式
    • 缓存一致
  • MQ

    • MQ
    • rabbitMQ
    • rocketMQ
    • kafka
  • 其它

    • 设计模式
    • 领域驱动(ddd)
  • 关系型数据库

    • mysql5.0
    • mysql8.0
  • 非关系型数据库

    • redis
    • mongoDB
  • 分布式/其他

    • ShardingSphere
    • 区块链
  • 向量数据库

    • M3E
    • OPEN AI
  • Jmeter
  • fiddler
  • wireshark
  • AI入门
  • AI大模型
  • AI插件
  • AI集成框架
  • 相关算法
  • AI训练师
  • 量化交易
  • gitee
  • github
  • infoq
  • osc
  • 砍材工具
  • 关于
  • 相关运营
  • docker
  • k8s
  • devops
  • nginx
  • 元宇宙
  • 区块链
  • 物联网
  • linux
  • webrtc
  • web3.0
  • gitee
  • github
  • infoq
  • osc
  • 砍材工具
  • 关于
  • 中考
  • 投资
  • 保险
  • 思
  • 微信记账小程序
  • java
  • redis
  • mysql
  • 场景类
  • 框架类
  • vuepress搭建
  • hexo搭建
  • 云图
  • llm wiki

    • 基于karpathy
    • gradle
  • 常用工具

    • git
    • gradle
    • Zadig
    • it-tools
    • 开源推荐
    • curl
  • 大前端

    • nodejs
    • npm
    • webpack
    • 微信
    • 正则
    • uniapp
    • app
  • java

    • java基础
    • jdk体系
    • jvm
    • spring
    • spring_cloud
    • spring_boot
    • 分库分表
    • zookeeper
  • python

    • python基础
    • python高级
    • python框架
  • 算法

    • 算法
  • 网关

    • spring_cloud_gateway
    • openresty
  • 高可用

    • 秒杀
    • 分布式
    • 缓存一致
  • MQ

    • MQ
    • rabbitMQ
    • rocketMQ
    • kafka
  • 其它

    • 设计模式
    • 领域驱动(ddd)
  • 关系型数据库

    • mysql5.0
    • mysql8.0
  • 非关系型数据库

    • redis
    • mongoDB
  • 分布式/其他

    • ShardingSphere
    • 区块链
  • 向量数据库

    • M3E
    • OPEN AI
  • Jmeter
  • fiddler
  • wireshark
  • AI入门
  • AI大模型
  • AI插件
  • AI集成框架
  • 相关算法
  • AI训练师
  • 量化交易
  • gitee
  • github
  • infoq
  • osc
  • 砍材工具
  • 关于
  • 相关运营
  • docker
  • k8s
  • devops
  • nginx
  • 元宇宙
  • 区块链
  • 物联网
  • linux
  • webrtc
  • web3.0
  • gitee
  • github
  • infoq
  • osc
  • 砍材工具
  • 关于
  • 中考
  • 投资
  • 保险
  • 思
  • 首页
    • 开发板介绍
    • micropython环境搭建
    • esp32开发板
    • 面包板
    • 万能表使用
  • 面包板
    • 点灯
  • esp32
    • 点亮开发板led灯
    • 点亮外接led
    • 点亮外接oled文字
    • 红外传感器
    • 红外传感器+olde
    • esp32+面包板
  • MQTT编程

    • MQTT入门

      • 物联网 MQTT
      • 物联网 MQTT和Socket
      • 物联网 MQTT订阅性能优势
      • 物联网 MQTT简易版Broker
    • HiveMQ

      • hivemq实战入门
    • Protobuf

      • Protobuf入门
      • Protobuf入门+梳理
      • Protobuf实战第一篇
    • emqx

      • emqx入门
    • mica

      • mica入门
    • netty

      • 入门

        • 基于netty构建入门
        • 理解粘包/拆包
        • 编解码器机制与自定义协议
        • 心跳和ack机制
        • mqtt服务demo演示
        • mqtt服务协议支持
        • mqtt服务udp支持
      • 协议规范

        • mqtt协议规范(发布/订阅模式)
        • mqtt协议规范(轻量级二进制协议)
        • mqtt协议规范(三种 QoS 等级)
        • mqtt协议规范(主题通配符订阅)
        • mqtt协议规范(遗嘱与保留消息)
      • 报文结构

        • 控制报文结构(报文分类)
        • 控制报文结构(连接与握手)
        • 控制报文结构(发布与接收)
      • 核心实战

        • 核心实战(握手与认证)
        • 核心实战(心跳保活机制)
        • 核心实战(会话管理)
        • 核心实战(安全)
    • netty-mqtt-simulator

      • 物联网终端模拟器

        • 设备模拟器设计
        • 设备模拟器教程
    • netty-mqtt-boot

      • 物联网实战

        • 模块化设计
        • 统一接入层
        • 消息路由与流转层
        • 核心服务层
        • 业务应用层
        • 整体项目管理
        • 测试脚手架
        • 兼容支持
  • 物联网 基于netty核心实战-安全tls
    • 简述
    • 源码(netty-sample-05-tls)
    • 混淆
    • 认证只在连接环节完成
    • 自己生成 jks 证书
    • 源码演示
      • 服务端代码
      • 客户端 代码
      • 演示结果

物联网 基于netty核心实战-安全tls

简述

TLS(传输层安全)在握手阶段建立加密通道,一旦握手成功,后续所有 MQTT 报文(包括 CONNECT、PUBLISH、SUBSCRIBE、PINGREQ、DISCONNECT 等)都会通过这个加密通道传输

源码(netty-sample-05-tls)

https://gitee.com/kcnf-iot/iot-sample/tree/master/netty/netty-sample-05

混淆

  • “TLS” 和 “SSL” 混用

SSL(Secure Sockets Layer) 是 TLS 的前身,已弃用(SSL 2.0/3.0 均有严重漏洞)

TLS 1.0、1.1、1.2、1.3 是现行标准。很多人仍习惯说 “SSL”,但实际指的都是 TLS。推荐只说 TLS

  • “启用 TLS” 不等于 “客户端身份认证”

启用 TLS(单向)时,只有服务端提供证书,客户端验证服务端身份。客户端可以是任何实体,不需要任何凭证

客户端身份认证 需要开启 双向 TLS(mTLS),即服务端也要求客户端提供证书。这才是“使用 X.509 证书认证”的常见含义

  • “证书” 和 “密钥” 混淆
证书:公钥 + 元数据 + CA 签名,是公开的,可以被任何人获取
私钥:必须保密,用于解密或签名。私钥丢失等同于身份被盗
常见错误:把证书文件(.crt)和私钥文件(.key)弄反,或者误以为证书中包含私钥
  • “自签名证书” vs “CA 签名证书”
自签名证书:自己给自己签发,没有第三方 CA 背书。浏览器会报“不安全”,适合内部测试。
CA 签名证书:由受信任的 CA 签发,生产环境必须使用。免费 CA(如 Let's Encrypt)或商业 CA
混淆点:认为自签名证书“不安全”是因为加密强度不够——实际上加密强度相同,问题在于无法验证身份的真实性,容易遭受中间人攻击
  • “TLS 握手” 和 “应用层认证” 混淆
TLS 握手发生在 TCP 连接之后、应用层数据(如 HTTP、MQTT)之前
握手完成后,应用层可以再进行自己的认证(如 HTTP Basic Auth、MQTT 用户名/密码)
混淆:以为 TLS 证书认证可以替代应用层认证。实际上它们是互补的,TLS 保证通道安全,应用层决定具体身份

认证只在连接环节完成

MQTT 协议规范中,身份验证(Authentication)是在 CONNECT 报文 处理时一次性完成

客户端发送 CONNECT 报文(其中可携带用户名/密码、JWT 等凭据)
Broker 验证凭据有效性
验证通过则回复 CONNACK(成功),并记录该连接为“已认证”状态
此后,该连接上的任何后续报文(订阅、发布等)不再重复进行身份认证,而是基于已认证的身份执行授权检查(ACL)

自己生成 jks 证书

  • 找到jdk安装bin目录 img

  • 执行如下命令

./keytool.exe -genkeypair -alias server -keyalg RSA -keysize 2048 -validity 365 -keystore server-keystore.jks -storepass password -keypass password -dname "CN=localhost, OU=MyOrgUnit, O=MyOrg, L=Shanghai, ST=Shanghai, C=CN"

img

  • 默认生成之后的文件bin目录下面 img

源码演示

服务端代码

package com.jysemel.iot;

import com.jysemel.iot.handler.MqttTlsHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
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.mqtt.MqttDecoder;
import io.netty.handler.codec.mqtt.MqttEncoder;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import lombok.SneakyThrows;

import javax.net.ssl.KeyManagerFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.KeyStore;

public class MqttTlsServer {
    public static void main(String[] args) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) {
                            ChannelPipeline p = ch.pipeline();
                            // 启动 TLS 加密服务 (8883) – 需要提供 keystore
                            SslContext sslContext = createSslContext();
                            p.addLast("ssl", sslContext.newHandler(ch.alloc()));
                            // (A) 使用 Netty 内置的 MqttDecoder
                            p.addLast("mqttDecoder", new MqttDecoder(1024));
                            // (B) 使用单例的 MqttEncoder
                            p.addLast("mqttEncoder", MqttEncoder.INSTANCE);
                            // (C) 实现我们自己的业务逻辑处理器
                            p.addLast("authHandler", new MqttTlsHandler());
                        }
                    });
            ChannelFuture f = b.bind(9883).sync();
            System.out.println("MQTT tls Server started on port 9883");
            f.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }



    @SneakyThrows
    private static SslContext createSslContext()  {
        String keyStorePath = "server-keystore.jks";
        String keyStorePass = "password";

        // 方式1:通过当前线程的上下文类加载器(最可靠)
        InputStream keyStoreStream = Thread.currentThread()
                .getContextClassLoader()
                .getResourceAsStream(keyStorePath);

        // 方式2:如果方式1失败,尝试直接使用文件系统(测试用)
        if (keyStoreStream == null) {
            File file = new File("src/main/resources/" + keyStorePath);
            if (file.exists()) {
                keyStoreStream = new FileInputStream(file);
                System.out.println("使用文件系统路径加载:" + file.getAbsolutePath());
            }
        }

        if (keyStoreStream == null) {
            throw new RuntimeException("无法加载证书文件: " + keyStorePath + "。请确保文件位于 resources 目录下,且已编译到 target/classes。");
        }

        KeyStore keyStore = KeyStore.getInstance("JKS");
        keyStore.load(keyStoreStream, keyStorePass.toCharArray());
        keyStoreStream.close();

        KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        kmf.init(keyStore, keyStorePass.toCharArray());

        System.out.println("成功加载 TLS 证书");
        return SslContextBuilder.forServer(kmf).build();
    }
}

客户端 代码

package com.jysemel.iot;

import com.jysemel.iot.handler.MqttTlsClientHandler;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
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.NioSocketChannel;
import io.netty.handler.codec.mqtt.MqttDecoder;
import io.netty.handler.codec.mqtt.MqttEncoder;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;

import javax.net.ssl.SSLException;

public class MqttTlsClient {
    public static void main(String[] args) throws Exception {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            // 创建 SSLContext(测试环境信任所有证书,生产环境需配置 TrustStore)
            SslContext sslContext = createSslContext();

            Bootstrap b = new Bootstrap();
            b.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) {
                            ChannelPipeline p = ch.pipeline();
                            // 添加 SslHandler(必须在编解码器之前)
                            p.addLast("ssl", sslContext.newHandler(ch.alloc(), "127.0.0.1", 9883));
                            p.addLast("mqttDecoder", new MqttDecoder(1024));
                            p.addLast("mqttEncoder", MqttEncoder.INSTANCE);
                            p.addLast("clientHandler", new MqttTlsClientHandler());
                        }
                    });
            ChannelFuture f = b.connect("127.0.0.1", 9883).sync();
            System.out.println("MQTT Client connected to TLS port 9883");
            f.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }

    private static SslContext createSslContext() throws SSLException {
        // 测试环境:信任所有服务端证书(不验证证书合法性,仅用于快速测试)
        // 生产环境绝不能这样使用!
        return SslContextBuilder.forClient()
                .trustManager(InsecureTrustManagerFactory.INSTANCE)
                .build();
    }
}

演示结果

imgimg

最近更新: 2026/5/28 16:59
Contributors: kcnf
Prev
核心实战(会话管理)