스마트디바이스실습 ‐ 14주차 - jiho0419/SmartDevice_2025-1 GitHub Wiki
💠 OAuth(Open Authorization) 는 제3자 애플리케이션이 사용자의 비밀번호를 직접 알지 않고도 사용자 리소스에 접근할 수 있도록 권한을 위임하는 개방형 표준 인증 프로토콜 을 말한다.
🔸 기존 인증 방식(예: 아이디/비밀번호 공유)은 보안상 취약했으며, 다양한 플랫폼과 서비스 간 연동을 어렵다는 문제를 해결하기 위해 설계되었다.
🔸API 보안, 모바일/웹 앱 인증, SSO(Single Sign-On) 등에 폭넓게 활용되고 있다.
구성 요소 | 설명 |
---|---|
Resource Owner (자원 소유자) | 일반적으로 사용자. 자신의 데이터를 제3자 앱에 제공할 권한을 가짐 |
Client (클라이언트 애플리케이션) | 사용자 자원에 접근하려는 제3자 앱 |
Resource Server (자원 서버) | 보호된 사용자 데이터를 저장하고 있음 (예: Google Drive) |
Authorization Server (인증 서버) | 권한 부여 요청을 승인하고 Access Token을 발급 |
🔸사용자 인증 요청
- 클라이언트는 인증 서버에 사용자 인증 및 권한 부여를 요청
🔸사용자 동의
- 사용자는 권한 요청을 검토하고 동의
🔸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 Type | 설명 및 사용 시나리오 |
---|---|
Authorization Code | 서버 기반 웹 애플리케이션에 적합 |
Implicit | 클라이언트 측 앱 (보안이 낮음, 현재는 비권장) |
Client Credentials | 사용자 없이 서버 간 인증 (예: 시스템 간 API 호출) |
Resource Owner Password | 사용자 자격 증명을 직접 전달 (보안상 권장되지 않음) |
Device Code | 키보드 없는 기기에서 사용 (예: 스마트 TV) |
Refresh Token | Access Token 만료 시 재발급에 사용됨 |
기업/플랫폼 | OAuth 사용 예시 |
---|---|
Gmail, Google Drive, YouTube API 등 | |
소셜 로그인, 사용자 데이터 연동 | |
GitHub | 개발자 인증, 토큰 기반 API 접근 |
Kakao / Naver | 소셜 로그인 및 사용자 정보 API |
💠 카카오톡을 이용하여 토양 센서 값을 실시간으로 수신하는 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
값을 위 코드에 적용하여 업로드 진행

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


