IoT-For-Beginners 农场项目实战:在 Raspberry Pi 与虚拟 IoT 设备中使用 X.509 证书连接 Azure IoT Hub
【免费下载链接】IoT-For-Beginners12 Weeks, 24 Lessons, IoT for All!项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners
本文基于 IoT-For-Beginners 课程的“保护你的植物”(Keep your plant secure)一课,讲解如何把土壤湿度传感器设备代码从共享密钥(connection string)认证切换为 X.509 证书认证。读完本文,你可以完整掌握:证书与私钥文件的落位方式、app.py中需要新增和替换的每一步代码(主机名、设备 ID、X509实例、create_from_x509_certificate客户端创建),以及如何在 Raspberry Pi 和 CounterFit 虚拟设备两种环境下验证遥测上报与直接方法(Direct Method)请求的收发。
一、背景:为什么把设备接入方式从连接字符串换成 X.509
在该项目的前几课中,土壤湿度传感器通过 IoT Hub 连接字符串接入云端。该连接字符串由三段以分号分隔的键值对组成(详见 本课 README):
| 键 | 示例值 | 含义 |
|---|---|---|
HostName | soil-moisture-sensor.azure-devices.net | IoT Hub 的 URL |
DeviceId | soil-moisture-sensor | 设备唯一标识 |
SharedAccessKey | Bhry+ind...(Base64) | 设备与 IoT Hub 共同持有的对称密钥 |
连接字符串方式本质是对称密钥认证:设备首次连接时用共享密钥对“URL + 过期时间戳”签名,生成 SAS 令牌发送给 IoT Hub 验证。这种方式的关键缺陷是把共享密钥硬编码在代码里——一旦源码泄露,任何人都可以克隆设备身份。X.509 证书方式则改用非对称加密的私钥/证书对:私钥留在设备上、绝不进入源码仓库,设备凭证书完成身份认证。这正是本节的改造目标。
改造前的设备代码见 迁移到云端一课的 app.py,其中第 7 行是connection_string = '<connection_string>',第 12 行用IoTHubDeviceClient.create_from_connection_string(connection_string)创建客户端。本次改造完成后,这两个位置会被证书认证逻辑取代,最终形态即 code/pi 目录下的 app.py。
二、前提:拿到设备身份文件与证书文件
X.509 接入方式所需的设备身份和证书文件由 Azure CLI 一步生成(本课 README 中给出):
az iot hub device-identity create --device-id soil-moisture-sensor-x509 \ --am x509_thumbprint \ --output-dir . \ --hub-name <hub_name>该命令在 IoT Hub 中注册设备 ID 为soil-moisture-sensor-x509(特意与上一课的设备区分),并在当前目录生成两个文件:
soil-moisture-sensor-x509-key.pem—— 设备私钥,绝不能提交到公共源码库;soil-moisture-sensor-x509-cert.pem—— 设备的 X.509 证书。
设备代码侧的第一步任务就是把这两个文件拷贝到包含设备代码的目录。文档特别提示:如果你是通过 VS Code Remote SSH 在 Raspberry Pi 上开发、而密钥是在 PC 或 Mac 上生成的,可以直接把文件拖进 VS Code 的资源管理器(Explorer)完成拷贝,省去 SCP 之类的额外操作。文件放好后,它们与app.py同级,代码中就可以用./相对路径引用。
三、修改 app.py:从连接字符串切换到证书认证的完整步骤
打开app.py,按以下 6 步修改。每一步都对应 code/pi/soil-moisture-sensor/app.py 中的一行成品代码,可直接对照。
3.1 定义 IoT Hub 主机名变量
在创建设备客户端之前,新增主机名变量:
host_name = "<host_name>"把<host_name>替换为你 IoT Hub 的主机名。获取方式:打开你现有连接字符串,读取其中HostName段的值——它就是你 IoT Hub 的名字,形如以.azure-devices.net结尾的域名。对照仓库成品代码,app.py 第 10 行写作:
host_name = "<host_name>.azure-devices.net"3.2 定义设备 ID 变量
紧接着声明设备 ID(对应 app.py 第 11 行):
device_id = "soil-moisture-sensor-x509"这个值必须与第 2 节az iot hub device-identity create中注册的--device-id完全一致,即soil-moisture-sensor-x509。
3.3 从 azure.iot.device 导入 X509 类
连接使用 X.509 时,需要一个持有证书文件的X509类实例。把X509加入从azure.iot.device模块导入的类列表中(对应 app.py 第 5 行):
from azure.iot.device import IoTHubDeviceClient, Message, MethodResponse, X509注意与改造前的 迁移到云端一课 app.py 第 5 行相比,唯一的变化就是末尾多了X509。
3.4 用证书与私钥文件创建 X509 实例
在host_name声明下方,用第 2 节生成的两个文件构造X509实例(对应 app.py 第 12 行):
x509 = X509("./soil-moisture-sensor-x509-cert.pem", "./soil-moisture-sensor-x509-key.pem")第一个参数是证书文件,第二个参数是私钥文件,均使用相对于app.py所在目录的路径——这就是第三节中要求“把文件拷贝到设备代码目录”的原因。
3.5 用 create_from_x509_certificate 替换客户端创建逻辑
找到原来用连接字符串创建device_client的那一行:
device_client = IoTHubDeviceClient.create_from_connection_string(connection_string)将其整体替换为(对应 app.py 第 14 行):
device_client = IoTHubDeviceClient.create_from_x509_certificate(x509, host_name, device_id)从源码结构看,替换后create_from_x509_certificate接收三个参数:X509实例(内含证书与私钥)、主机名、设备 ID——三者共同替代了原来“一个连接字符串变量”的角色。之后device_client.connect()建立的就是基于 X.509 证书的双向认证连接,而不是 SAS 签名连接。
3.6 删除 connection_string 变量
最后,删除包含connection_string变量的那一行。此时设备代码中不再存在任何对称密钥,SharedAccessKey彻底移出代码。
四、虚拟设备(CounterFit)版本:与 Pi 版本的差异
如果你不打算上真实硬件,而是用 CounterFit 在 PC 上跑虚拟设备,流程完全相同,但app.py的硬件抽象层不同。对比 code/virtual-device/soil-moisture-sensor/app.py 与 Pi 版本,差异集中在文件头部:
from counterfit_connection import CounterFitConnection CounterFitConnection.init('127.0.0.1', 5000) import time from counterfit_shims_grove.adc import ADC from counterfit_shims_grove.grove_relay import GroveRelay虚拟版通过CounterFitConnection.init('127.0.0.1', 5000)连接本地 CounterFit 服务器(默认 5000 端口),并从counterfit_shims_grove导入ADC与GroveRelay垫片;而 Pi 版直接从grove.adc/grove.grove_relay导入(见 Pi 版 app.py 第 2–3 行)。从第 10 行开始的 X.509 认证部分(host_name、device_id、x509 实例、create_from_x509_certificate)在两个版本中逐行一致,因此本文第三节的修改步骤对两种环境通用。
五、验证运行:遥测上报与直接方法请求
修改完成后运行代码。设备主循环(Pi 版 app.py 第 33–40 行)每 10 秒执行一轮:
adc.read(0)读取 Grove 模拟输入模块通道 0 上的土壤湿度传感器模拟量;- 把读数打包为 JSON
{'soil_moisture': <value>},通过device_client.send_message(message)发送遥测; time.sleep(10)休眠后进入下一轮。
与此同时,第 20–31 行注册了直接方法回调handle_method_request:收到名为relay_on的请求时调用relay.on()(Grove 继电器接在端口 5),收到relay_off时调用relay.off(),并以状态码 200 回复MethodResponse。
验证要点(与文档任务清单一致):
- 控制台先打印
Connecting,随后打印Connected,说明设备已用 X.509 证书完成与 IoT Hub 的连接; - 在 IoT Hub 监控(或你之前的遥测查看方式)中能看到持续到达的土壤湿度遥测;
- 沿用之前课练习过的方式向设备发送
relay_on/relay_off直接方法请求,设备日志应打印Direct method received - relay_on(或relay_off),继电器状态随之切换,并且云端收到 200 响应。
三项全部满足,即表明“土壤湿度传感器程序已使用 X.509 证书接入 IoT Hub”,改造完成。
六、适用范围与注意事项
- 适用设备:本节针对 Raspberry Pi(单板计算机)与虚拟 IoT 设备两条路线。Wio Terminal(Arduino)路线目前不可用——wio-terminal-x509.md 明确指出,撰写时点的 Azure Arduino SDK 尚不支持 X.509 证书,需要实验证书认证时应参考本节的 Python SDK 方案。
- 私钥安全:
soil-moisture-sensor-x509-key.pem是设备身份的核心凭证,与共享密钥一样不能提交到公共源码库;code/pi 目录下的app.py只包含证书文件路径引用,不包含密钥内容,这是可以安全入库的形态。 - 自签证书的边界:本课通过
az iot hub device-identity create --am x509_thumbprint生成的是自签名证书,适合学习场景;正式生产环境不应使用自签名证书(详见 本课 README 的 X.509 章节)。 - 时间准确性:无论 SAS 还是证书认证,设备时钟都必须准确(通常由 NTP 提供),否则连接或令牌校验会失败。
七、小结与进一步探索
本节改造的净变化可以概括为三处:导入X509并实例化(指向./soil-moisture-sensor-x509-cert.pem与./soil-moisture-sensor-x509-key.pem)、用create_from_x509_certificate(x509, host_name, device_id)替换create_from_connection_string(connection_string)、删除connection_string变量。设备行为(10 秒周期的土壤湿度遥测、relay_on/relay_off直接方法)保持不变,变的是身份认证从“共享对称密钥”升级为“设备私钥 + X.509 证书”,从根上消除了克隆设备身份的风险。
想继续深入时,建议按以下路径阅读:
- 6-keep-your-plant-secure/README.md —— 加密基础、对称/非对称密钥、SAS 令牌机制与 X.509 证书原理的完整课程背景;
- code/pi/soil-moisture-sensor/app.py 与 code/virtual-device/soil-moisture-sensor/app.py —— 本节的完整成品代码(Pi 版与虚拟设备版);
- 4-migrate-your-plant-to-the-cloud/code/pi/soil-moisture-sensor/app.py —— 改造前的连接字符串版本,可用于逐行 diff 对比;
- 6-keep-your-plant-secure/assignment.md —— 本课结业作业:构建一个新的 IoT 设备。
【免费下载链接】IoT-For-Beginners12 Weeks, 24 Lessons, IoT for All!项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考