全部產品
Search
文件中心

IoT Platform:一型一密動態註冊(MQTT通道)

更新時間:Jun 30, 2024

本文以一型一密預註冊的Java代碼為例,介紹基於MQTT通訊協定的裝置,如何進行動態註冊並擷取接入物聯網平台認證需要的DeviceSecret。

前提條件

已完成一型一密文檔中的以下步驟:

  1. 建立產品。

  2. 開啟動態註冊。

  3. 如果是一型一密預註冊,需要添加裝置。

  4. 產線燒錄。

背景資訊

物聯網平台支援多種裝置安全認證方式,具體認證方式,請參見裝置安全認證

物聯網平台支援基於MQTT通道的一型一密預註冊和免預註冊認證。相關流程和參數說明,請參見基於MQTT協議的裝置動態註冊

準備開發環境

本樣本使用的開發環境如下:

操作步驟

  1. 開啟IntelliJ IDEA,建立一個Maven工程。例如MQTT動態註冊
  2. 在工程中的pom.xml檔案中,添加Maven依賴,然後單擊Load Maven Changes表徵圖,完成依賴包下載。
    <dependency>
      <groupId>org.eclipse.paho</groupId>
      <artifactId>org.eclipse.paho.client.mqttv3</artifactId>
      <version>1.2.1</version>
    </dependency>
    
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>1.2.83</version>
    </dependency>
  3. 在工程MQTT動態註冊的路徑/src/main/java下,建立Java類。例如DynamicRegisterByMqtt,輸入以下代碼。
    說明
    • 裝置未啟用時,可進行多次動態註冊,裝置的DeviceSecret以最後一次為準。請確保固化到裝置的DeviceSecret為最新。
    • 裝置已啟用時,您需調用ResetThing介面重設雲端裝置動態註冊狀態為未註冊,才能再次動態註冊該裝置。
    import java.nio.charset.StandardCharsets;
    import java.util.Random;
    import java.util.Set;
    import java.util.SortedMap;
    import java.util.TreeMap;
    
    import javax.crypto.Mac;
    import javax.crypto.spec.SecretKeySpec;
    
    import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
    import org.eclipse.paho.client.mqttv3.MqttCallback;
    import org.eclipse.paho.client.mqttv3.MqttClient;
    import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
    import org.eclipse.paho.client.mqttv3.MqttException;
    import org.eclipse.paho.client.mqttv3.MqttMessage;
    import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
    
    import com.alibaba.fastjson.JSONObject;
    
    /**
     * 裝置動態註冊。 
     */
    public class DynamicRegisterByMqtt {
    
        // 地區ID,填寫您的產品所在地區ID。
        private static String regionId = "cn-shanghai";
    
        // 定義加密方式。可選MAC演算法:HmacMD5、HmacSHA1、HmacSHA256,需和signmethod取值一致。
        private static final String HMAC_ALGORITHM = "hmacsha1";
    
        // 接收物聯網平台下發裝置認證的Topic。無需建立,無需訂閱,直接使用。
        private static final String REGISTER_TOPIC = "/ext/register";
    
        /**
         * 動態註冊。
         * 
         * @param productKey 產品的ProductKey
         * @param productSecret 產品金鑰
         * @param deviceName 裝置名稱
         * @throws Exception
         */
        public void register(String productKey, String productSecret, String deviceName) throws Exception {
    
            // 接入網域名稱,只能使用TLS。
            String broker = "ssl://" + productKey + ".iot-as-mqtt." + regionId + ".aliyuncs.com:1883";
    
            // 表示用戶端ID,建議使用裝置的MAC地址或SN碼,64字元內。
            String clientId = productKey + "." + deviceName;
    
            // 擷取隨機值。
            Random r = new Random();
            int random = r.nextInt(1000000);
    
            // securemode只能為2表示只能使用TLS;signmethod指定簽名演算法。
            String clientOpts = "|securemode=2,authType=register,signmethod=" + HMAC_ALGORITHM + ",random=" + random + "|";
    
            // MQTT接入用戶端ID。
            String mqttClientId = clientId + clientOpts;
    
            // MQTT接入使用者名稱。
            String mqttUsername = deviceName + "&" + productKey;
    
            // MQTT接入密碼,即簽名。
            JSONObject params = new JSONObject();
            params.put("productKey", productKey);
            params.put("deviceName", deviceName);
            params.put("random", random);
            String mqttPassword = sign(params, productSecret);
    
            // 通過MQTT connect報文進行動態註冊。
            connect(broker, mqttClientId, mqttUsername, mqttPassword);
        }
    
        /**
         * 通過MQTT connect報文發送動態註冊資訊。
         * 
         * @param serverURL 動態註冊網域名稱地址
         * @param clientId 用戶端ID
         * @param username MQTT使用者名稱
         * @param password MQTT密碼
         */
        @SuppressWarnings("resource")
        private void connect(String serverURL, String clientId, String username, String password) {
            try {
                MemoryPersistence persistence = new MemoryPersistence();
                MqttClient sampleClient = new MqttClient(serverURL, clientId, persistence);
                MqttConnectOptions connOpts = new MqttConnectOptions();
                connOpts.setMqttVersion(4);// MQTT 3.1.1
                connOpts.setUserName(username);// 使用者名稱
                connOpts.setPassword(password.toCharArray());// 密碼
                connOpts.setAutomaticReconnect(false); // MQTT動態註冊協議規定必須關閉自動重連。
                System.out.println("----- register params -----");
                System.out.print("server=" + serverURL + ",clientId=" + clientId);
                System.out.println(",username=" + username + ",password=" + password);
                sampleClient.setCallback(new MqttCallback() {
                    @Override
                    public void messageArrived(String topic, MqttMessage message) throws Exception {
                        // 僅處理動態註冊返回訊息。
                        if (REGISTER_TOPIC.equals(topic)) {
                            String payload = new String(message.getPayload(), StandardCharsets.UTF_8);
                            System.out.println("----- register result -----");
                            System.out.println(payload);
                            sampleClient.disconnect();
                        }
                    }
    
                    @Override
                    public void deliveryComplete(IMqttDeliveryToken token) {
                    }
    
                    @Override
                    public void connectionLost(Throwable cause) {
                    }
                });
                sampleClient.connect(connOpts);
            } catch (MqttException e) {
                System.out.print("register failed: clientId=" + clientId);
                System.out.println(",username=" + username + ",password=" + password);
                System.out.println("reason " + e.getReasonCode());
                System.out.println("msg " + e.getMessage());
                System.out.println("loc " + e.getLocalizedMessage());
                System.out.println("cause " + e.getCause());
                System.out.println("except " + e);
                e.printStackTrace();
            }
        }
    
        /**
         * 動態註冊簽名。
         * 
         * @param params 簽名參數
         * @param productSecret 產品金鑰
         * @return 簽名十六進位字串
         */
        private String sign(JSONObject params, String productSecret) {
    
            // 請求參數按字典順序排序。
            Set<String> keys = getSortedKeys(params);
    
            // sign、signMethod除外。
            keys.remove("sign");
            keys.remove("signMethod");
    
            // 組裝簽名明文。
            StringBuffer content = new StringBuffer();
            for (String key : keys) {
                content.append(key);
                content.append(params.getString(key));
            }
    
            // 計算簽名。
            String sign = encrypt(content.toString(), productSecret);
            System.out.println("sign content=" + content);
            System.out.println("sign result=" + sign);
    
            return sign;
        }
    
        /**
         * 擷取JSON對象排序後的key集合。
         *
         * @param json 需要排序的JSON對象
         * @return 排序後的key集合
         */
        private Set<String> getSortedKeys(JSONObject json) {
            SortedMap<String, String> map = new TreeMap<String, String>();
            for (String key : json.keySet()) {
                String value = json.getString(key);
                map.put(key, value);
            }
            return map.keySet();
        }
    
        /**
         * 使用HMAC_ALGORITHM加密。
         * 
         * @param content 明文
         * @param secret 密鑰
         * @return 密文
         */
        private String encrypt(String content, String secret) {
            try {
                byte[] text = content.getBytes(StandardCharsets.UTF_8);
                byte[] key = secret.getBytes(StandardCharsets.UTF_8);
                SecretKeySpec secretKey = new SecretKeySpec(key, HMAC_ALGORITHM);
                Mac mac = Mac.getInstance(secretKey.getAlgorithm());
                mac.init(secretKey);
                return byte2hex(mac.doFinal(text));
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
    
        /**
         * 二進位轉十六進位字串。
         * 
         * @param b 位元組
         * @return 十六進位字串
         */
        private String byte2hex(byte[] b) {
            StringBuffer sb = new StringBuffer();
            for (int n = 0; b != null && n < b.length; n++) {
                String stmp = Integer.toHexString(b[n] & 0XFF);
                if (stmp.length() == 1) {
                    sb.append('0');
                }
                sb.append(stmp);
            }
            return sb.toString().toUpperCase();
        }
    
        public static void main(String[] args) throws Exception {
    
            String productKey = "a1IoK******";
            String productSecret = "6vEu5Qlj5S******";
            String deviceName = "OvenDevice01";
    
            // 進行動態註冊。
            DynamicRegisterByMqtt client = new DynamicRegisterByMqtt();
            client.register(productKey, productSecret, deviceName);
    
            // 動態註冊成功,需要在本地固化deviceSecret。
        }
    }
  4. 在以上代碼中配置實際裝置相關參數。
    參數樣本說明
    regionIdcn-shanghai您的物聯網平台服務所在地區ID。地區代碼錶達方法,請參見地區列表
    productKeya1IoK******已燒錄至裝置的產品ProductKey,可登入物聯網平台控制台,在產品詳情頁查看。
    productSecret6vEu5Qlj5S******已燒錄至裝置的產品ProductSecret,可登入物聯網平台控制台,在產品詳情頁查看。
    deviceNameOvenDevice01您裝置的名稱。

    因裝置啟用時會校正DeviceName,建議您採用可以直接從裝置中讀取到的ID,如裝置的MAC地址、IMEI或SN碼等,作為DeviceName使用。

    broker"ssl://" + productKey + ".iot-as-mqtt." + regionId + ".aliyuncs.com:1883"裝置動態註冊的存取點。格式為如下ssl://" + "${YourInstanceDomain}" + ":" +1883

    其中${YourInstanceDomain}為MQTT接入網域名稱。擷取方法,請參見查看和配置執行個體終端節點資訊(Endpoint)

  5. 運行程式檔案DynamicRegisterByMqtt.java,使裝置攜帶DeviceName和所屬產品的ProductKey、ProductSecret向雲端發起認證請求。

    執行結果如圖所示,物聯網平台校正通過後,裝置接收到雲端下發的DeviceSecret(8d1f0cdab49dd229cf3b75**********)。

    代碼運行結果

後續步驟

裝置獲得串連雲端所需的裝置認證(ProductKey、DeviceName和DeviceSecret)後,您再使用MQTT用戶端,將裝置接入物聯網平台,進行資料通訊。

具體操作,請參見Paho-MQTT Java接入樣本