本文以一型一密預註冊的Java代碼為例,介紹基於MQTT通訊協定的裝置,如何進行動態註冊並擷取接入物聯網平台認證需要的DeviceSecret。
前提條件
已完成一型一密文檔中的以下步驟:
建立產品。
開啟動態註冊。
如果是一型一密預註冊,需要添加裝置。
產線燒錄。
背景資訊
物聯網平台支援多種裝置安全認證方式,具體認證方式,請參見裝置安全認證。
物聯網平台支援基於MQTT通道的一型一密預註冊和免預註冊認證。相關流程和參數說明,請參見基於MQTT協議的裝置動態註冊。
準備開發環境
本樣本使用的開發環境如下:
- 作業系統:Windows 10
- JDK版本:JDK8
- 整合式開發環境:IntelliJ IDEA社區版
操作步驟
- 開啟IntelliJ IDEA,建立一個Maven工程。例如MQTT動態註冊。
- 在工程中的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>
- 在工程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。 } }
- 在以上代碼中配置實際裝置相關參數。
參數 樣本 說明 regionId cn-shanghai 您的物聯網平台服務所在地區ID。地區代碼錶達方法,請參見地區列表。 productKey a1IoK****** 已燒錄至裝置的產品ProductKey,可登入物聯網平台控制台,在產品詳情頁查看。 productSecret 6vEu5Qlj5S****** 已燒錄至裝置的產品ProductSecret,可登入物聯網平台控制台,在產品詳情頁查看。 deviceName OvenDevice01 您裝置的名稱。 因裝置啟用時會校正DeviceName,建議您採用可以直接從裝置中讀取到的ID,如裝置的MAC地址、IMEI或SN碼等,作為DeviceName使用。
broker "ssl://" + productKey + ".iot-as-mqtt." + regionId + ".aliyuncs.com:1883" 裝置動態註冊的存取點。格式為如下 ssl://" + "${YourInstanceDomain}" + ":" +1883
。其中${YourInstanceDomain}為MQTT接入網域名稱。擷取方法,請參見查看和配置執行個體終端節點資訊(Endpoint)。
- 運行程式檔案DynamicRegisterByMqtt.java,使裝置攜帶DeviceName和所屬產品的ProductKey、ProductSecret向雲端發起認證請求。
執行結果如圖所示,物聯網平台校正通過後,裝置接收到雲端下發的DeviceSecret(
8d1f0cdab49dd229cf3b75**********
)。
後續步驟
裝置獲得串連雲端所需的裝置認證(ProductKey、DeviceName和DeviceSecret)後,您再使用MQTT用戶端,將裝置接入物聯網平台,進行資料通訊。
具體操作,請參見Paho-MQTT Java接入樣本。