스마트디바이스실습 ‐ 14주차 - jiho0419/SmartDevice_2025-1 GitHub Wiki

1️⃣ OAuth 2.0

*️⃣ 정의

💠 OAuth(Open Authorization) 는 제3자 애플리케이션이 사용자의 비밀번호를 직접 알지 않고도 사용자 리소스에 접근할 수 있도록 권한을 위임하는 개방형 표준 인증 프로토콜 을 말한다.

*️⃣ 필요성

🔸 기존 인증 방식(예: 아이디/비밀번호 공유)은 보안상 취약했으며, 다양한 플랫폼과 서비스 간 연동을 어렵다는 문제를 해결하기 위해 설계되었다.
🔸API 보안, 모바일/웹 앱 인증, SSO(Single Sign-On) 등에 폭넓게 활용되고 있다.

*️⃣ 구성 요소

구성 요소 설명
Resource Owner (자원 소유자) 일반적으로 사용자. 자신의 데이터를 제3자 앱에 제공할 권한을 가짐
Client (클라이언트 애플리케이션) 사용자 자원에 접근하려는 제3자 앱
Resource Server (자원 서버) 보호된 사용자 데이터를 저장하고 있음 (예: Google Drive)
Authorization Server (인증 서버) 권한 부여 요청을 승인하고 Access Token을 발급

*️⃣ 작동 방식 (Authorization Code Flow)

💠 절차 요약

🔸사용자 인증 요청

  • 클라이언트는 인증 서버에 사용자 인증 및 권한 부여를 요청

🔸사용자 동의

  • 사용자는 권한 요청을 검토하고 동의

🔸Authorization Code 수신

  • 인증 서버는 사용자 동의 후, Authorization Code를 리디렉션 URI로 전달

🔸Access Token 요청

  • 클라이언트는 이 코드를 인증 서버에 전달하고, Access Token을 요청

🔸Access Token 발급

  • 인증 서버는 클라이언트에게 Access Token을 발급

🔸API 요청

  • 클라이언트는 Access Token을 사용하여 자원 서버에서 사용자 데이터를 요청

💠 흐름 다이어그램

+--------+                               +---------------+
|        |--(A) Authorization Request -->|               |
|        |                               | Authorization |
|        |<-(B) Authorization Code ------|     Server    |
|        |                               +---------------+
|        |
| Client |                               +---------------+
|        |--(C) Access Token Request --->|               |
|        |     (with Auth Code)          |               |
|        |<-(D) Access Token ------------|               |
+--------+                               | Resource      |
                                         |    Server     |
                                         +---------------+    

*️⃣ Grant Types (권한 부여 유형)

Grant Type 설명 및 사용 시나리오
Authorization Code 서버 기반 웹 애플리케이션에 적합
Implicit 클라이언트 측 앱 (보안이 낮음, 현재는 비권장)
Client Credentials 사용자 없이 서버 간 인증 (예: 시스템 간 API 호출)
Resource Owner Password 사용자 자격 증명을 직접 전달 (보안상 권장되지 않음)
Device Code 키보드 없는 기기에서 사용 (예: 스마트 TV)
Refresh Token Access Token 만료 시 재발급에 사용됨

*️⃣ 실제 적용 사례

기업/플랫폼 OAuth 사용 예시
Google Gmail, Google Drive, YouTube API 등
Facebook 소셜 로그인, 사용자 데이터 연동
GitHub 개발자 인증, 토큰 기반 API 접근
Kakao / Naver 소셜 로그인 및 사용자 정보 API

2️⃣ 실습

*️⃣ 카카오톡으로 토양 센서 값 전송

💠 카카오톡을 이용하여 토양 센서 값을 실시간으로 수신하는 IoT 실습 진행

💠 준비물

🔸 ESP32
🔸 ESP32 확장 쉴드
🔸 토양 습도 센서


💠 회로 연결

ESP32 실드 토양 습도 센서
S : D34 S
VCC VCC
GND GND

💠 코드

#include <WiFi.h>
#include <HTTPClient.h>

const char *ssid = "Your_SSID";          // 사용하는 WiFi 네트워크 이름 (SSID)
const char *password = "Your_Password";  // 사용하는 WiFi 네트워크 비밀번호

const String rest_api_key = "REST API KEY"; 
String access_token = "Access token";
String refresh_token = "Refresh token";

#define MsgSendInterval 3600 // 60 * 60 초, 즉 한시간 간격으로 전송
long timeout = 3600;  //시간을 초로 나타냄
int sensorValue = 0;
int sensorPin = 34; // 토양 습도 센서 핀

void setup() {
  Serial.begin(115200);

  WiFi.begin(ssid, password);
  Serial.println("Connecting");
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(1000);
  }
  Serial.print("\nConnected to WiFi : ");
  Serial.println(WiFi.localIP());
}
void loop() {
  if (timeout++ > MsgSendInterval)  // 1시간(60 * 60)에 1번씩 전송
  {
    if (isAccessTokenExpired() == true) { //access token 만료 여부 확인
      if (update_access_token() == false) { // access token 재발급
        Serial.println("Access token update failed");
      }
    }
    sensorValue = analogRead(sensorPin);//토양 센서값 읽기
    send_message();
    timeout = 0;
  }

  delay(1000);
}


// str문자열에서 start_string와 end_string사이의 문자열을 추출하는 함수
String extract_string(String str, String start_string, String end_string) {
  int index1 = str.indexOf(start_string) + start_string.length();
  int index2 = str.indexOf(end_string, index1);
  String value = str.substring(index1, index2);
  return value;
}

bool isAccessTokenExpired() {
  HTTPClient http;
  bool returnVal = true;
/*
curl -v -X GET "https://kapi.kakao.com/v1/user/access_token_info" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}"
*/
  if (!http.begin("https://kapi.kakao.com/v1/user/access_token_info")) {
    Serial.println("\nfailed to begin http\n");
  }
  http.addHeader("Authorization", "Bearer " + access_token);

  int httpCode = http.GET();

  // httpCode will be negative on error
  if (httpCode > 0) {
    // file found at server
    if (httpCode == HTTP_CODE_OK) {
      String payload = http.getString();
      Serial.println(payload);
      String expireT = extract_string(payload, "\"expires_in\":", ",");
      Serial.println(expireT.toInt());
      if (expireT.toInt() > 0) {
        returnVal = false;
      } else {
        returnVal = true;
      }
    }
  } else {
    Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
  }
  http.end();
  return returnVal;
}

void send_message() {
  HTTPClient http;
  String url = "https://kapi.kakao.com/v2/api/talk/memo/default/send";
  if (!http.begin(url)) {
    Serial.println("\nfailed to begin http\n");
  }
  http.addHeader("Authorization", "Bearer " + access_token);
  http.addHeader("Content-Type", "application/x-www-form-urlencoded");

  int http_code;
  /*
   template_object={
        "object_type": "text",
        "text": "텍스트 영역입니다. 최대 200자 표시 가능합니다.",
        "link": {
            "web_url": "https://developers.kakao.com",
            "mobile_web_url": "https://developers.kakao.com"
        },
        "button_title": "바로 확인"
    }
    */
  String data = String("template_object={") + 
                String("\"object_type\": \"text\",") + 
                String("\"text\": \"") + String("토양 센서 값 :") + 
                String(sensorValue) +  //토양 센서 값
                String("\",\"link\": {}}"); //link가 없으면 오류메세지 받음
  Serial.println(data);
  http_code = http.POST(data);
  Serial.print("HTTP Response code: ");
  Serial.println(http_code);

  String response;
  if (http_code > 0) {
    response = http.getString();
    Serial.println(response);
  }

  http.end();
}

/*
curl -v -X POST "https://kauth.kakao.com/oauth/token" \
 -H "Content-Type: application/x-www-form-urlencoded" \
 -d "grant_type=refresh_token" \
 -d "client_id=${REST_API_KEY}" \
 -d "refresh_token=${USER_REFRESH_TOKEN}"
*/
bool update_access_token() {
  HTTPClient http;
  bool retVal = false;

  String url = "https://kauth.kakao.com/oauth/token";
  String new_refresh_token = "";
  if (!http.begin(url)) {
    Serial.println("\nfailed to begin http\n");
  }
  http.addHeader("Content-Type", "application/x-www-form-urlencoded");
  int http_code;
  String data = "grant_type=refresh_token&client_id=" + rest_api_key + "&refresh_token=" + refresh_token;
  Serial.println(data);
  http_code = http.POST(data);
  Serial.print("HTTP Response code: ");
  Serial.println(http_code);

  String response;
  if (http_code > 0) {
    response = http.getString();
    Serial.println(response);
    access_token = extract_string(response, "{\"access_token\":\"", "\"");
    new_refresh_token = extract_string(response, "\"refresh_token\":\"", "\"");
    //만료 1개월전부터 갱신되므로 data가 없을 수도 있음
    if (new_refresh_token != "") {
      refresh_token = new_refresh_token;
    }
    retVal = true;
  } else {
    retVal = false;
  }
  http.end();
  return retVal;
}

💠 실행 방법

🔸 카카오톡 앱 설정
🔸 https://developers.kakao.com/ 접속 > 회원가입 / 로그인
🔸 내 어플리케이션 > 애플리케이션 추가하기
🔸 앱 이름, 회사명 ESP32 설정 > 카테고리 교육 설정 > 저장


🔸 REST API 확인
🔸 앱 설정 > 앱 키 > REST API 키 복사 - Access 토큰 발급 사용됨


🔸 Web 플랫폼 등록
🔸 앱 설정 > 플랫폼 > Web > Web 플랫폼 등록 클릭
🔸 사이트 도메인을 http://localhost 설정 > 저장


🔸 카카오 로그인 활설화
🔸 카카오 로그인 > 활성화 설정 > ON 으로 변경 > 활성화 클릭


🔸 Redirect URL 등록
🔸 카카오 로그인 > Redirect URL 등록 클릭
🔸 Redirect URL을 https://www.example.com/oauth 설정 > 저장


🔸 카카오톡 메시지 설정
🔸 카카오 로그인 > 동의항목 > 카카오톡 메시지 설정 > 설정 클릭
🔸 선택 동의 클릭 > 동의목적은 ESP32 개발 설정 > 저장


🔸 Redirect URI로 인가 코드(Authorization code) 전달 받기
🔸 https://kauth.kakao.com/oauth/authorize?response_type=code&client_id="REST API키"&redirect_uri=https://www.example.com/oauth
🔸 REST API 키는 위에 복사한 키를 입력 후 URL 접속 > 동의 후 확인하고 계속하기 클릭
🔸 나온 URL의 code = ??? 복사 > AUTHORIZE_CODE로 사용됨


🔸 Access 토근 발급
🔸 Access 토큰 발급을 위해 curl 명령 방법을 사용하여 진행
🔸 cmd - 명령 프롬포트 접속 > 밑에 코드를 입력

curl -v -X POST "https://kauth.kakao.com/oauth/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "client_id={REST_API_KEY}" \
--data-urlencode "redirect_uri={REDIRECT_URI}" \
-d "code={AUTHORIZATION_CODE}"

🔸 REST_API_KEY > 앱 키에서 복사한 REST API Key 값 입력
🔸 REDIRECT_URI > 위에 REDIRECT_URI으로 설정한 https://www.example.com/oauth 입력
🔸 AUTHORIZATION_CODE > 바로 전 단계에서 인가 받은 AUTHORIZATION_CODE 입력
🔸 입력하면 밑에 사진처럼 access_token 값과 refresh_token 값을 받을 수 있음
🔸 access_token refresh_token rest_api_key 값을 위 코드에 적용하여 업로드 진행


💠 실습 결과

🔸 실시간으로 토양 센서 값이 카카오톡으로 전송 되는 것을 확인할 수 있다.

⚠️ **GitHub.com Fallback** ⚠️