투케이2K

165. (TWOK/LOGIC) [Aws] 자바스크립트 paho-mqtt 라이브러리 사용해 IAM 계정 인증 및 Aws Iot Core 플릿 프로비저닝 수행 로직 정리 본문

투케이2K 로직정리

165. (TWOK/LOGIC) [Aws] 자바스크립트 paho-mqtt 라이브러리 사용해 IAM 계정 인증 및 Aws Iot Core 플릿 프로비저닝 수행 로직 정리

투케이2K 2025. 12. 15. 19:32
728x90

[로직 정리]

정리 로직 : Aws / Web / JavaScript / 프로비저닝

상태 : [Aws] 자바스크립트 paho-mqtt 라이브러리 사용해 IAM 계정 인증 및 Aws Iot Core 플릿 프로비저닝 수행 로직 정리

 

[설 명]

// --------------------------------------------------------------------------------------
[사전) 설정 및 정보 확인 사항]
// --------------------------------------------------------------------------------------

1. 제 목 : [Aws] 자바스크립트 paho-mqtt 라이브러리 사용해 IAM 계정 인증 및 Aws Iot Core 플릿 프로비저닝 수행 로직 정리


2. 테스트 환경 : Aws / Iot / Mqtt / WebSocket / JavaScript / Paho.MQTT


3. 사전) MQTT (Message Queuing Telemetry Transport) 설명 : 

  >> MQTT 는 경량 메시지 프로토콜로, 주로 IoT(사물인터넷) 환경에서 사용됩니다

  >> MQTT 목적 : 제한된 네트워크 환경(저속, 불안정)에서 효율적으로 메시지를 주고받기 위해 설계

  >> MQTT 기반 : TCP/IP 위에서 동작

  >> MQTT 패턴 : Publish/Subscribe 모델을 사용

    - Publisher : 메시지를 발행하는 클라이언트

    - Subscriber : 특정 주제(topic)를 구독하는 클라이언트

    - Broker: 메시지를 중개하는 서버 (예: Mosquitto)

  >> MQTT 주요 특징 : 

    - 경량성 : 헤더가 매우 작음(2바이트부터 시작)

    - QoS (Quality of Service) : 
      $ QoS 0: 최대 한 번 전달(보장 없음)
      $ QoS 1: 최소 한 번 전달(중복 가능)
      $ QoS 2: 정확히 한 번 전달(가장 안전)

    - 지속 연결 : KeepAlive로 연결 상태 유지

    - Last Will and Testament (LWT) : 클라이언트 비정상 종료 시 브로커가 메시지 발행

    - 토픽 기반 라우팅 : 계층적 구조(/home/temperature 등)


4. 사전) 클레임에 의한 프로비저닝 설명 : 

  >> 디바이스는 프로비저닝 클레임 인증서 및 프라이빗 키 (특수 용도의 자격 증명) 가 내장된 상태로 제조될 수 있습니다

  >> 디바이스에 내장 된 인증서가 AWS IoT에 등록된 경우, 이 서비스에서 해당 인증서를 디바이스가 일반 작업에 사용할 수 있는 고유한 디바이스 인증서와 교환할 수 있습니다

  >> 클레임에 의한 프로비저닝 예시 :

    - [1] 디바이스에서 프로비저닝에 필요한 인증서 내장된 상태로 제조

    - [2] 디바이스에서 Aws 접속에 필요한 엔드포인트 정보 및 인증서를 사용해 Aws 연결 수행 실시

    - [3] CreateProvisioningTemplate 을 호출하여 프로비저닝 템플릿을 생성

    - [4] 템플릿 생성 완료 후 응답으로 내려온 생성 된 사물 Thing Name 값 내부 저장 후 기기 연결 시 사용


5. 사전) 자바스크립트에서 MQTT 연결을 위한 Paho 라이브러리 및 aws4fetch 의존성 설정 코드 : 

  <!-- [CDN 주소 설정] : https://www.jsdelivr.com/package/npm/paho-mqtt -->
  <script src="https://sdk.amazonaws.com/js/aws-sdk-2.1416.0.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/paho-mqtt@1.1.0/paho-mqtt.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/dayjs/1.10.7/dayjs.min.js"></script>


  <!-- [자바스크립트 코드 작성 부분에 module 로드 지정] -->
  <script type="module">

    // [모듈 import]
    import aws4 from "https://esm.sh/aws4fetch@1.0.17";

    const { AwsClient } = aws4;

  </script>

// --------------------------------------------------------------------------------------






// --------------------------------------------------------------------------------------
[로직 설명]
// --------------------------------------------------------------------------------------

1. 자바스크립트 <script> 코드 내에 AWS 연결 및 프로비저닝을 위한 전역 변수 선언 수행

  // [AWS] : IAM 및 접속 정보
  const endpoint = 'a1..d9-ats.iot.ap-northeast-1.amazonaws.com'; // [AWS iot:Data-ATS 도메인]
  const region = 'ap-northeast-1'; // [AWS 리전]
  const accessKey = 'AK..7Q'; // [IAM 액세스 키]
  const secretKey = 'Zz..xj'; // [IAM 시크릿 키]


  // [AWS 생성 된 사물 Thing]
  const requestThingName = "TWOK_9999999999"; // 생성 요청 Thing 
  var responseThingName = ""; // 응답 받은 Thing 


  // [AWS Iot Core 에 등록 요청 템플릿 명칭]
  const templateName = "TEMP_DEVICE";


  // 구독 토픽 : 임시 인증서 생성 요청 및 템플릿 등록 요청

  var subscribeTopicList = new Array();

  const subscribe_topic_Certificate_Accepted_Path = `$aws/certificates/create/json/accepted`;
  const subscribe_topic_Certificate_Rejected_Path = `$aws/certificates/create/json/rejected`;
  const subscribe_topic_Template_Accepted_Path = `$aws/provisioning-templates/${templateName}/provision/json/accepted`;
  const subscribe_topic_Template_Rejected_Path = `$aws/provisioning-templates/${templateName}/provision/json/rejected`;


  // 메시지 전송 토픽 : 임시 인증서 생성 요청 및 템플릿 등록 요청
  const publish_topic_Certificate_Create_Path = `$aws/certificates/create/json`;
  const publish_topic_Template_Create_Path = `$aws/provisioning-templates/${templateName}/provision/json`;

  const publish_json_template_create = {
      certificateOwnershipToken: "", // 임시 인증서 발급에서 받은 값
      Parameters: {
          CertificateId: "", // 임시 인증서 발급에서 받은 값
          MacAddress: "AA:BB:CC:DD:EE:01",
          DeviceId: requestThingName,
          DeviceKey: "ABCDEFG",
          DeviceType: "H",
          Environment: "DEV",
          SerialNumber: "123456789012",
          Version: "1.0.1"
      }
  };

  var temp_certificateId = ""; // 임시 사물 인증서 정보 관련
  var temp_certificatePem = ""; // 임시 사물 인증서 정보 관련
  var temp_privateKey = ""; // 임시 사물 인증서 정보 관련
  var temp_certificateOwnershipToken = ""; // 임시 사물 인증서 정보 관련


2. 자바스크립트 window.onload 부분에서 AWS SigV4 인증 객체 생성 수행

  const aws = new AwsClient({
      accessKeyId: accessKey,
      secretAccessKey: secretKey,
      service: "iotdevicegateway", // ✅ 중요!
      region: region,
  });

  const url = `wss://${endpoint}/mqtt`; // [AWS iot:Data-ATS 도메인]


3. aws.sign 을 사용해 SigV4 서명된 URL 생성 실시

  const signed = await aws.sign(url, { 
      method: "GET",
      signQuery: true // ✅ signQuery: true 옵션 추가
  });

  // ✅ wss://a1y..pd9-ats.iot.ap-northeast-1.amazonaws.com/mqtt?X-Amz-Date=20251123T234432Z&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AK..H7Q%2F20251123%2Fap-northeast-1%2Fiotdevicegateway%2Faws4_request&X-Amz-SignedHeaders=host&X-Amz-Signature=fcc..d7a
  const signUrl = signed.url;


4. Paho 라이브러리를 사용해 AWS 연결 수행을 위한 MQTT Client 객체 생성

  const clientId = "clientId-" + Math.random(); // MQTT 연결 클라이언트 아이디

  const mqttClient = new Paho.Client(signUrl, clientId); // ✅ AWS WebSocket signUrl 사용 : wss:// 443 자동 할당


5. MQTT Client 객체에 MQTT 연결 이벤트 감지를 위한 콜백 핸들러 함수 지정

  mqttClient.onConnectionLost = onConnectionLost; // MQTT 연결 유실 이벤트 감지

  mqttClient.onMessageArrived = onMessageArrived; // MQTT 실시간 메시지 송수신 이벤트 감지

  ..... 

  function onConnectionLost(responseObject) {
    console.error("[onConnectionLost] : ", JSON.stringify(responseObject));
  }

  function onConnectionFail(error) {
    console.error("[onConnectionFail] : ", error.message);
  }


6. MQTT Client 객체 connect 연결 시도 수행

  mqttClient.connect({ // Custom Option
                          
      useSSL: true, // ✅ SSL/TLS 사용 여부 : 반드시 true 설정

      mqttVersion: 4, // MQTT 프로토콜 버전 (MQTT v3.1.1)

      timeout: 20, // 연결 시도 타임아웃 (초) → 기본값은 30초
      keepAliveInterval: 30, // ✅ Ping 주기 (초) → 기본값은 60초    
      
      cleanSession: true, // ✅ 이전 세션 구독 정보 삭제

      onSuccess: () => { // 연결에 따른 콜백        
          console.warn("[AWS] Connection Success : ", url);        

          onConnect(); // Call Connect Success
      }, 
      onFailure: (err) => { // 연결에 따른 에러 메시지        
          console.error("[AWS] Connection failed : ", err)        

          onConnectionFail(err); // Call Connect Fail
      } 
  });


7. 정상적으로 MQTT Client connect 가 완료 된 경우 onConnect 함수에서 프로비저닝 동작 토픽 Subscribe 구독 수행 실시

  function onConnect() {
      console.log("[Mqtt] : connect : onSuccess");

      // --------------------------------------
      // Logic : Procedure
      // --------------------------------------
      try {

          // ---------------------------------
          // ✅ AWS : Subscribe
          // ---------------------------------
          onSubscribe(subscribe_topic_Certificate_Rejected_Path);
          onSubscribe(subscribe_topic_Certificate_Accepted_Path);
          onSubscribe(subscribe_topic_Template_Rejected_Path);
          onSubscribe(subscribe_topic_Template_Accepted_Path);
          // ---------------------------------                

      }
      catch (exception) {
          console.error("[onConnect] : [Exception] : 예외 상황 발생 : ", exception);
      }
      
  }


8. onSubscribe 토픽 구독 수행 함수에서 Certificate 인증서 발급 관련 토픽이 모두 구독이 완료 된 경우 임시 인증서 발급 요청 수행

  mqttClient.subscribe(topic, { // Custom Set Topic Subscribe

      qos: QOS, // 네트워크 서비스 품질
      timeout: SUBSCRIBE_TIME_OUT, // SUBACK 대기 시간 (초)
      invocationContext: { topic }, // ✅ 토픽을 컨텍스트에 담아둠

      onSuccess: (resp) => {

          var timeStamp = String(Date.now()); // 타임스탬프
          var date = dayjs(Number(timeStamp));
          var nowDate = date.format("YYYY-MM-DD HH:mm:ss");

          // 브로커가 승인한 QoS와 함께, 내가 담아둔 topic 을 확인 가능
          console.log("");
          console.log("=========================================");
          console.log("[Mqtt] : onSubscribe : onSuccess");
          console.log("---------------------------------------");
          console.log("[nowDate] : ", nowDate);
          console.log("---------------------------------------");
          console.log("[topic] : ", resp.invocationContext.topic);
          console.log("---------------------------------------");
          console.log("[QOS] : ", resp.grantedQos);
          console.log("=========================================");
          console.log("");

          try {
              if (subscribeTopicList.indexOf(resp.invocationContext.topic) < 0){ // 토픽 구독 리스트 배열에 특정 값을 포함하지 않는 경우
                  subscribeTopicList.push(resp.invocationContext.topic);                        
              }

              console.warn("[Subscribe Topic List Total Count] : ", subscribeTopicList.length);


              // ✅ 인증서 생성 요청에 대한 토픽이 모두 구독 된 경우 
              if (subscribeTopicList.indexOf(subscribe_topic_Certificate_Accepted_Path) >= 0 && subscribeTopicList.indexOf(subscribe_topic_Certificate_Rejected_Path) >= 0){                                
                  console.warn("[Mqtt] : onSubscribe : Certificate Path Validation Check : Success");

                  // ---------------------------------
                  // ✅ AWS : Publish : 임시 인증서 생성 요청
                  // ---------------------------------
                  onPublish(publish_topic_Certificate_Create_Path, ""); 
                  // ---------------------------------
              }


              // ✅ 템플릿 생성 요청에 대한 토픽이 모두 구독 된 경우 
              if (subscribeTopicList.indexOf(subscribe_topic_Template_Accepted_Path) >= 0 && subscribeTopicList.indexOf(subscribe_topic_Template_Rejected_Path) >= 0){                                
                  console.warn("[Mqtt] : onSubscribe : Template Path Validation Check : Success");
              }

          }
          catch (exception) {
              console.error("[onSubscribe] : [Topic List Add] : 예외 상황 발생 : ", exception.message);
          }
      },
      onFailure: (err) => {
          console.error("");
          console.error("=========================================");
          console.error("[onSubscribe] : [구독 수행] : onFailure : ", err);
          console.error("=========================================");
          console.error("");
      }
  });


9. 임시 사물 인증서 발급 요청에 대한 onMessage 실시간 메시지 이벤트가 감지 된 경우 응답 받은 임시 인증서 데이터 파싱 및 템플릿 등록 요청 publish 수행

function onMessageArrived(message) {

  if (message.destinationName == subscribe_topic_Certificate_Accepted_Path){ // ✅ 임시 사물 인증서 발급 요청 응답 토픽
                
      /*
      {
          "certificateId": "688..e2c",
          "certificatePem": "-----BEGIN CERTIFICATE-----\nMIIDWTCC..Z+yxpf\n-----END CERTIFICATE-----\n",
          "privateKey": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpA..zpvlMuQi0g==\n-----END RSA PRIVATE KEY-----\n",
          "certificateOwnershipToken": "ey..pUIn0="
      }
      // */

      try {

          const temp_json = JSON.parse(message.payloadString);

          if (temp_json.hasOwnProperty("certificateId") == true && temp_json.hasOwnProperty("certificatePem") == true && temp_json.hasOwnProperty("privateKey") == true && temp_json.hasOwnProperty("certificateOwnershipToken") == true){

              temp_certificateId = temp_json.certificateId;
              temp_certificatePem = temp_json.certificatePem;
              temp_privateKey = temp_json.privateKey;
              temp_certificateOwnershipToken = temp_json.certificateOwnershipToken;
              
              console.warn("[Mqtt] : onMessageArrived : Parsing : Certificate : Success");

              publish_json_template_create.certificateOwnershipToken = temp_certificateOwnershipToken; // ✅ 임시 인증서 토큰
              publish_json_template_create.Parameters.CertificateId = temp_certificateId; // ✅ 임시 인증서 아이디

              onPublish(publish_topic_Template_Create_Path, JSON.stringify(publish_json_template_create)); // ✅ 템플릿 등록 요청 Publish
          }
          else {
              console.error("[Mqtt] : onMessageArrived : Parsing : Certificate : Error : certificateId , certificatePem , privateKey , certificateOwnershipToken Key Not Found"); 
          }

      }
      catch (error){
          console.error("[Mqtt] : onMessageArrived : Parsing : Certificate : Exception : ", error);
      }
  }

};


10. 템플릿 등록 요청에 대한 onMessage 실시간 메시지 이벤트가 감지 된 경우 응답 받은 Response ThingName 데이터 파싱 수행


function onMessageArrived(message) {

  if (message.destinationName == subscribe_topic_Template_Accepted_Path){ // ✅ 템플릿 등록 요청 응답 토픽
                
      /*
      {"deviceConfiguration":{},"thingName":"TWOK_9999999999"}
      // */

      try {

          const temp_json = JSON.parse(message.payloadString);

          if (temp_json.hasOwnProperty("thingName") == true){

              responseThingName = temp_json.thingName; // ✅ 응답 받은 ThingName 파싱

              console.warn("[Mqtt] : onMessageArrived : Parsing : Template : Success");
          }
          else {
              console.error("[Mqtt] : onMessageArrived : Parsing : Template : Error : thingName Json Key Not Found"); 
          }

      }
      catch (error){
          console.error("[Mqtt] : onMessageArrived : Parsing : Template : Exception : ", error);
      }
  }

};


10. AWS 콘솔 대시보드 및 자바스크립트 describeThing Api 호출을 통해 정상적으로 Thing 이 생성 되었는지 확인 수행

// --------------------------------------------------------------------------------------






// --------------------------------------------------------------------------------------
[참고 사이트]
// --------------------------------------------------------------------------------------

[Web/JavaScript] paho-mqtt 라이브러리 사용해 AWS IOT Core 플릿 프로비저닝 Thing 사물 등록 수행

https://kkh0977.tistory.com/8491

https://blog.naver.com/kkh0977/224109136460


[간단 소스] 자바스크립트 paho-mqtt 라이브러리 및 aws4fetch 사용해 AWS IOT MQTT 연결을 위한 WebSocket 주소 확인

https://kkh0977.tistory.com/8417

https://blog.naver.com/kkh0977/224084144227


[간단 소스] 자바스크립트 paho-mqtt 라이브러리 사용해 커스텀 구독 Subscribe , Publish 메시지 발생 객체 정의

https://blog.naver.com/kkh0977/224086668570

// --------------------------------------------------------------------------------------
 
728x90
반응형
Comments