본문으로 건너뛰기

Gitops 알아보기

· 약 7분
한우영 (hanu)
새내기 휠 인턴

Gitops?

Gitops는 Devops 방법론중 한가지로, Git을 사용해 인프라 변경상황 및 버전을 관리하는 방식입니다. Gitops 방식을 채택하여 인프라를 관리할 경우 모든 변경사항의 추적과 롤백이 쉬워지며, 인프라의 현재 상태를 실시간으로 확인할 수 있게 됩니다.

특히 다수의 개발자와 Sysops가 협업하는 경우 변경사항과 변경 요청을 명확하게 관리할 수 있습니다.

Gitops 환경을 구성하기 위해서는 선언적 인프라(Declarative Infrastructure) 가 우선적으로 전제되어야 합니다. 선언적 인프라는 인프라를 원하는 상태로 선언하고, 실제 인프라가 해당 상태로 유지되도록 하는것을 의미합니다.

이를 위해 상태 저장소(Config Repository) 를 구성하는것이 일반적이며, 상태 저장소에는 인프라의 현 상태를 정의하는 선언적인 코드들이 관리됩니다.

GitOps Principles and Workflows Every Team Should Know | Rafay <이미지 출처 : https://rafay.co>

Stack for Gitops

Gitops를 구성하기 위해 필요한 요소는 크게 3가지로 나눌 수 있습니다.

  1. CI/CD Pipeline
  2. Infra Automation / IaC
  3. Git repository (Config Repository)

하나씩 설명을 해보자면, 우선 CI/CD 파이프라인은 선언적 인프라 코드의 변경사항이 자동적으로 테스트되고 인프라에 배포가 이루어져야 합니다.

두번째로 Infra Automation 및 IaC(Infrastructure as Code) 의 경우, 인프라를 코드로 정의하고 코드를 통해 인프라를 생성/관리/삭제 할 수 있도록 하는 기술입니다. 보통 클라우드 환경에서는 Terraform을 많이 사용하고, 온프레미스가 병합된 하이브리드 환경에서는 Ansible이나 Chef를 사용하는 경우도 많습니다.

세번째 Git repository는 위에서 짧게 언급한 상태저장소로, 모든 코드 및 인프라 변경사항은 깃 레포에 저장되어야 합니다. 이를 위해서 Git 레포지토리를 사용하게 되고 이는 Gitops의 가장 큰 장점인 변경사항의 빠른 트래킹과 롤백이 용이하다는 장점을 살릴 수 있도록 합니다.

Why Gitops?

Gitops 의 가장 큰 장점은 코드로써 인프라를 작성하고 Git을 통해 관리하게 되어 생기는 장점인 "롤백 용이성" 과 "변경이력 추적이 용이" 하다는 부분입니다.

부수적인 효과로는 IaC 를 사용하다 보니 인프라의 변경에 대한 검증과 배포를 더욱 효과적으로 자동화 할 수 있고, 이를 통해 개발자가 많은 시간을 쓰지않고 안정적인 배포를 구성할 수 있게됩니다.

Limit of Gitops & Hard way to gitops

현 시점에서 Gitops 의 가장 큰 한계는 복잡성과 보안성입니다.

초기 구축을 위해 많은 시간이 들어가고, 인프라의 규모가 커질수록 관리해야 하는 코드의 규모가 커지니 이에 대한 복잡도 또한 자연스럽게 높아질 수 밖에 없습니다.

여러 클라우드 벤더나 서버 유형을 사용하는 경우거나 모듈화된 인프라가 고려된 경우라면 상태저장소를 분할하여 관리할 수 있겠지만, 대부분의 경우에서는 인프라의 모듈화는 구성하기 어려운 경우가 많습니다.

또한 보안성의 관점에서 인프라를 코드로 정의하고 코드를 Git에 관리하기 때문에 비밀키가 포함된 환경변수의 공유 과정이나 내부 접근통제의 과정에서 보안문제가 발생할 수 있습니다.

이를 해결하기 위해서는 암호화와 비밀키 공유에 대한 적절한 절차 구성, 그리고 엄격한 접근 권한 관리가 필요합니다.

Nevertheless..

위에서 서술한 단점인 인프라 복잡도가 높아지고, 보안성에 상대적으로 단점이 있음에도 불구하고 Gitops는 여러 장점들로 인해 최근 많은 조직에서 채택되고 있는 Devops 방법론입니다.

위에 언급한 장점 이외에도 비 기술적인 관점에서 개발팀에게 더욱 친화적인 인프라 운영 경험을 제공할 수 있고, 초기 구성단계를 지나고 나면 Sysops(운영)의 관점에서 역할이 많이 줄어들기 때문에 많은 조직에서 사랑받는 방법론이 아닐까 예상합니다.

오늘 저녁은 여러분들의 토이프로젝트에 Gitops 구성을 한번 해보시는거 어떨까요?

FlutterInAppWebView와 Front-end 간의 통신 방법

· 약 4분
손성민 (happycastle)
Taxi 팀 앱 개발자

안녕하세요. Taxi팀 앱 개발 담당하고 있는 손성민(happycastle)입니다.

Flutter로 하이브리드 앱 개발을 진행하다보면, 웹에서 앱에 토큰을 전달하거나, 로그인 상태를 공유하는 등 Front-end 간 통신할 필요성이 생깁니다.

Flutter에서 주로 사용되는 WebView 라이브러리는 FlutterInAppWebView, FlutterWebView가 있지만, FlutterInAppWebView에서 사용되는 방식에 대해 다루고자 합니다.

Javascript Handler

Javascript -> Flutter

InAppWebViewController에 JavascriptHandler를 등록하고, Front-end에서 해당 Javascript Handler를 호출하여 Flutter 코드를 실행시킬 수 있습니다.

controller.addJavaScriptHandler(handlerName: 'myHandlerName', callback: (args) {
return {
'bar': 'bar_value', 'baz': 'baz_value'
};
});

다음과 같이 Javascript Handler를 Controller에 등록할 수 있습니다.

매개변수를 받을 수 있으며, 매개변수의 형식은 List 형태로 전달됩니다. 매개변수가 들어간 순서대로 List에 추가되게 됩니다. 딕셔너리 형식으로 매개변수를 전달한 경우에도, Dart의 Map과 같은 방식으로 값을 가져올 수 있습니다. dart:convert 라이브러리의 jsonEncode를 사용하여 자동으로 변환합니다.

전달할 인자도 return을 사용해서 javascript handler의 반환값으로 사용할 수 있습니다.

비동기를 지원하여 async, await를 사용하여 함수를 구현할수도 있습니다.

다음과 같이 Front-end에서 Flutter에 등록된 Javascript Handler를 호출할 수 있습니다.

window.flutter_inappwebview.callHandler(handlerName, ...args)

다만, flutterInAppWebViewPlatformReady Event가 발생하고 난 이후부터 Event Handler 호출을 할 수 있습니다.

그래서 다음과 같이 Flutter Webview에서 호출을 받을 준비가 되었는지 여부를 체크해야합니다.

var isFlutterInAppWebViewReady = false;
window.addEventListener("flutterInAppWebViewPlatformReady", function(event) {
isFlutterInAppWebViewReady = true;
});

Flutter -> Javascript

반대로 Javascript 함수를 Flutter에서 호출하는 것도 가능합니다. 간단하게 InAppWebViewController.evaluateJavascript 를 사용하면 됩니다.

다음은 공식 문서에 있는 예시 코드입니다.

controller.evaluateJavascript(source: "new XMLSerializer().serializeToString(document);");

비동기함수를 실행하기 위해서는 InAppWebViewController.callAsyncJavaScript 를 사용할 수 있습니다.

다음은 공식 문서에 존재하는 예시 코드 입니다.

onLoadStop: (controller, url) async {
final String functionBody = """
var p = new Promise(function (resolve, reject) {
window.setTimeout(function() {
if (x >= 0) {
resolve(x);
} else {
reject(y);
}
}, 1000);
});
await p;
return p;
""";

var result = await controller.callAsyncJavaScript(
functionBody: functionBody,
arguments: {'x': 49, 'y': 'my error message'});
print(result?.value.runtimeType); // int
print(result?.error.runtimeType); // Null
print(result); // {value: 49, error: null}

result = await controller.callAsyncJavaScript(
functionBody: functionBody,
arguments: {'x': -49, 'y': 'my error message'});
print(result?.value.runtimeType); // Null
print(result?.error.runtimeType); // String
print(result); // {value: null, error: my error message}
},

참고 및 인용자료

[FlutterInAppWebView v6.0.0 공식문서] : https://inappwebview.dev/docs/webview/javascript/injection

기술과 사진의 접점, 계산사진학 속으로

· 약 32분
정상 (macintosh)
남는 건 사진 뿐 📸

당신 곁에 있는 카메라가 최고의 카메라입니다

10년 전 사람들에게 사진이 잘 나오는 카메라를 물어보았다면, 아마 이런 걸 보여줬을 것입니다.

eos 5d mark iii
[그림 1] 캐논 EOS 5D Mark III. 사진 출처: 위키미디어 공용

2012년에 출시한 캐논 EOS 5D Mark III입니다. 렌즈를 제외한 본체의 무게만 생수 500mL 두 병에 달했으며, 출시 당시 무려 429만원에 판매되었다고 합니다. 크고 무거운 데다 잘 쓰려면 상당한 사진 지식이 필요했겠죠.

이제 여러분 옆에 있는 친구분에게 사진이 잘 나오는 카메라를 물어봅시다. 주머니에서 바로 아이폰 14 프로갤럭시 S23 울트라를 꺼내 보여주시는 분들도 계시겠지만, 여전히 많은 경우에는 저렇게 거대한 DSLR이나 미러리스를 말씀하실 겁니다. 솔직히 그렇잖아요? 아무리 폰카가 잘 나온다 잘 나온다 하지만, 우리는 여전히 사진을 찍을 때 저런 카메라가 있는 스튜디오를 가고 싶어합니다. 콘서트에서 직캠을 찍고 싶을 때에도 망원렌즈가 달린 저런 카메라를 대여하려고 하고요.

그런데요, 그런 DSLR이나 미러리스를 만드는 곳에서는 다른 생각을 하고 있습니다. 여기 테루시 시미즈라는 분이 계십니다. 소니와 니콘 DSLR에 들어가는 이미지 센서와, 전세계 스마트폰 센서의 44% (출처)를 제조하는 소니 반도체 사업부의 CEO이신데요. 2022년 사업 설명회에서 이런 발언을 남겼습니다.

&quot;테루시 시미즈 씨. 사업설명회에서 발언 중&quot;
[그림 2] 연설 중이신 테루시 시미즈 씨. 사진 출처: 닛케이 기사(원문)

"앞으로 몇 년 안에 (스마트폰으로 찍은) 정지 사진이 SLR 카메라의 화질을 능가할 것이라 예상합니다." (출처: 닛케이 기사(원문))

시미즈 씨는 구체적인 시기로 2024년을 언급하면서, "더 큰 센서"와 "더 넓은 구경의 렌즈", 그리고 "계산사진학 기술(Computational Photography)"을 결합해 스마트폰 카메라의 화질이 DSLR을 능가할 것이라고 말했습니다.

내년이면 스마트폰이 DSLR을 추월한다니. 놀랍습니다. 시미즈 씨의 발언을 조금 더 자세히 살펴볼까요? 저 세 가지 중 더 큰 센서와 더 넓은 구경의 렌즈는 그럭저럭 이해가 갑니다. 당장 우리의 폰의 "카툭튀"가 그걸 보여주고 있으니까요.

그럼에도, 여전히 스마트폰의 카메라 센서는 기존 카메라에 비하면 터무니없게 작습니다. 센서가 크다는 갤럭시 S23 울트라도 겨우 1/1.3"입니다. 기존 DSLR에 비하면 1/12배에 불과한 면적입니다. 도대체 마지막 세 번째, "계산사진학 기술"은 어떻게 센서 크기에 의한 사진의 질 차이를 메꾸는 것일까요?

오늘은 계산사진학이 어떻게 등장했는지, 그리고 어떤 예시가 있는지 가볍게 살펴보도록 하겠습니다. 아, OpenCV를 이용해 직접 체험해보실 수 있는 간단한 코딩 예제도 포함되어 있어요.

계산사진학의 시초, 스티브 만(Steve Mann)

계산사진학(Computational Photography). 처음 들었을 때 용어가 다소 낯설게 느껴지시는 분도 계실 텐데요. 계산사진학의 영역은 지금도 확대되고 있기 때문에, 이 학문에 대한 하나의 정의를 내릴 수는 없습니다(Raskar, 2007). 계산사진학은 "사진이 잘 나오는 카메라"가 아니라 "사진이 잘 나오는 카메라를 만드는 방법"입니다. 어떤 환경에서 어떤 사람이 찍든, 잘 나온 것 같은 사진을 만들기 위한 알고리즘을 개발하고 이를 실제 제품에 적용하는 학문입니다.

계산사진학을 처음 만든 분이 바로 아래에 있습니다.

MIT에서 웨어러블 컴퓨터의 시초를 만든 스티브 만(Steve Mann)입니다.

스티브 만씨. 1980년의 프로토타입을 착용 중
[그림 3] 1980년, 웨어러블 컴퓨터를 착용하고 계신 스티브 만씨. 사진 출처: http://www.wearcam.org/ieeecomputer/r2025.htm

네, 지금 보기에는 조금 불편해 보이죠. 그래도 당시 PC에 쓰이던 최신 MOS 6502 프로세서와 화면 표시를 위한 1.5인치 CRT 디스플레이를 장착하고도 머리에 착용할 수 있었던 크기로 만들어낸 기술이 놀라워 보입니다.

이 프로토타입은 세계 최초의 웨어러블 컴퓨터이기도 합니다. 이 컴퓨터 만으로도 할 수 있는 이야기가 너무나도 많지만, 오늘 집중하고 싶은 것은 이 컴퓨터가 어떤 용도로 쓰였는지입니다.

바로 아래 사진을 찍기 위해서였습니다.

Steve Mann씨의 작품
[그림 4] Steve Mann씨의 작품. 사진 출처: http://www.wearcam.org/ieeecomputer/r2025.htm

어떻게 이런 사진을 찍을 수 있었을까요? 이 사진을 찍을 당시에는 디지털 카메라가 없었습니다. 포토샵도요. 스티브 만씨는 "Light-Painting"이라는 기법을 사용하였습니다.

정적인 풍경을 사진으로 담을 때, 한 색의 조명을 한 건물에 비추어두고, 조명의 색상과 위치를 바꾸어가며 여러 장의 사진을 합성하면 위와 같은 사진을 얻을 수 있습니다. 스티브 만씨의 컴퓨터는 조명의 위치를 기억하여 현재의 시야 위에 합성해줌으로써 이를 가능하게 했습니다. 컴퓨터가 수행한 연산의 도움을 받아 독창적인 사진을 만들어낸 것이지요. 이것이 바로 계산사진학의 시초입니다.

놀랍게도 오늘날 스마트폰에 적용되고 있는 계산사진학의 원리도 위 사진의 것과 크게 다르지 않습니다. 핵심은 "여러 장의 사진을 쌓아 하나의 사진을 만들어내는 것"입니다. 우리가 생각해볼 수 있는 것은, "여러 장의 사진"을 만들어내는 과정과, 그 사진을 "쌓아" 하나의 사진으로 만들어내는 과정 두 가지이겠지요. 이제 스마트폰에서 이 두 과정이 어떻게 이루어지는지 살펴보도록 하겠습니다.

스마트폰의 계산사진학

스마트폰의 계산사진학의 핵심은 작지만 그 덕분에 읽기 속도가 빠른 카메라 센서와 기존 카메라보다 높은 성능의 처리 장치입니다. 빠른 센서는 여러 장의 사진을 서로 다른 조건으로 빠르게 촬영할 수 있게 하고, 고성능의 CPU, GPU, NPU는 그 사진들을 하나의 사진으로 합성하는 것을 가능케 합니다 (이 과정을 "쌓음(Stacking)"이라고 표현합니다). 우리가 흔히 쓰는 카메라의 기능들이 어떻게 구현되어 있는지 간단하게 살펴보겠습니다.

1. 파노라마 - 방향을 바꾸어가며 쌓아나가기

경치 좋은 휴양지를 가게 되면 항상 써볼까 말까 고민하게 되는 기능이죠! 파노라마는 오늘 소개해드릴 예시 중 가장 고전적인 예시입니다. 2011년 애플은 아이폰 5를 발표하면서 스마트폰에 처음으로 파노라마 기능을 도입했습니다.

iPhone 5 파노라마 광고 스크린샷
[그림 5] 아이폰 5의 파노라마 기능 광고 스크린샷. 사진 출처: https://www.youtube.com/watch?v=fqrI0EUxYUg

아이폰에서 파노라마를 촬영할 때에는 가속도계 센서를 사용하여 사용자가 아이폰을 움직이는 속력을 측정하고, 위 그림처럼 사각형 형태의 가이드라인을 화면 상에 그려 사용자가 일정한 방향과 속력으로 풍경을 촬영하도록 유도합니다. 그렇지만 파노라마 기술 자체는 이러한 가속도계 센서 없이 사진 여러 장만 있어도 구현할 수 있습니다. 바로 호모그래피(Homography) 기술 덕분입니다. 호모그래피는 한 사진을 이루는 평면과 평면과 다른 사진을 이루는 평면 사이의 대응 관계를 나타내는 행렬입니다.

호모그래피
[그림 6] 두 장의 사진을 찍을 때 호모그래피.

[그림 6]에서 카메라가 서로 다른 위치에서 서로 다른 방향을 보며 두 장의 사진을 찍습니다. 카메라에 담기는 영역을 빨간색 삼각형으로 표시해보았습니다(카메라가 얼마 만큼의 풍경을 담을 수 있는지는 카메라 렌즈의 화각에 의해 결정됩니다). 이 때 두 사진은 분명 같은 평면 위에 있지 않습니다만, 두 사진이 공통적으로 포함하고 있는 대상들을 찾아내고, 그 대상들이 각각의 사진에서 어느 좌표에 그려지는지를 알면 두 평면 사이의 대응 관계를 구할 수 있습니다. 이렇게 구한 대응 관계를 이용하여 두 사진을 합성하면 [그림 7]과 같이 됩니다.

파노라마
[그림 7] 두 장의 사진을 합성한 파노라마.

정리하면 파노라마 사진을 합성하는 과정은 아래와 같습니다.

  1. 두 사진에서 사진이 회전되거나 선형변환되어도 변하지 않는 대상들을 찾아냅니다. 이 대상들을 keypoint라고 부르는데, SIFT, SURF 등의 알고리즘을 사용하여 찾아낼 수 있습니다.
  2. 두 사진의 keypoint들이 서로 각각 어떻게 대응되는지를 찾아냅니다. 이 과정을 feature matching이라고 합니다. 각 사진의 keypoint의 개수가 많지 않다면, 각각의 keypoint들을 일일이 비교하는 brute-force 방식을 사용해도 됩니다.
  3. feature matching의 결과를 바탕으로 homography matrix를 구합니다. homography matrix의 각 원소를 구하기 위해서는 4개의 대응 관계만 있으면 됩니다.
  4. 구한 homography matrix를 이용하여 한 사진을 다른 사진의 평면으로 변환시킵니다. 이제 두 사진은 같은 평면에 있기 때문에, 두 사진을 붙일 수 있습니다.

Towards Data Science의 예시 코드를 참조해보셔도 좋습니다.

이처럼 파노라마는 기계학습 없이도 구현할 수 있는 기술입니다. 이어 소개할 HDR도 그렇습니다!

2. HDR - 노출을 바꾸어가며 쌓아나가기

여러분이 스마트폰 카메라에서 셔터 한 번을 누를 때, 실제로 찍히는 사진이 한 장이 아니라는 것을 알고 계시나요? 제가 사용하는 갤럭시의 기본 카메라 앱 설정에 들어가면 아래와 같이 HDR 설정이 항상 켜져있는 것을 볼 수 있습니다.

HDR ON
[그림 8] "HDR ON".

아래는 제 카메라로 찍은 일출입니다. 저희 눈으로 보는 하늘과 건물의 모습과는 너무 다르죠. 카메라 센서가 받아들일 수 있는 빛 신호의 최대 세기와 최소 세기 사이의 간격은 그렇게 크지 않습니다. 저희가 일반적으로 쓰는 카메라는, 센서가 한 번에 담을 수 있는 가장 밝은 부분과 가장 어두운 부분의 밝기 차이가 1,000배 정도에 불과합니다(이를 다이나믹 레인지(DR, Dynamic Range)가 좁다라고 합니다). 하늘을 기준으로 노출을 맞추니 건물이 검게 나오고, 건물을 기준으로 노출을 맞추니 하늘이 하얗게 나옵니다.

NO HDR
[그림 9] 역광 상황에서의 카메라 사진 (하늘 기준으로 노출을 맞춤).
NO HDR
[그림 10] 역광 상황에서의 카메라 사진 (건물 기준으로 노출을 맞춤).

그런데, 우리는 이미 파노라마 사진을 만드는 원리로부터 여러 장의 사진들에서 겹치는 부분들을 찾아낼 수 있다는 것을 알고 있습니다. 마찬가지로 하늘이 잘 나온 사진에서 하늘에 대한 색 정보를 가져오고, 건물이 잘 나온 사진에서 건물에 대한 색 정보를 가져와서 합성하면 어떨까요? 이렇게 하면 하늘과 건물이 모두 잘 나온 사진을 얻을 수 있을 것입니다. 이것이 바로 High Dynamic Range(HDR) 사진입니다.

직접 HDR 사진을 만들어 보겠습니다. Debevec et al.의 방법을 사용하겠습니다. 이 방법에서는, 노출 시간이 짧은 사진은 밝은 부분을 잘 담아내고, 노출 시간이 긴 사진은 어두운 부분을 잘 담아낼 것이라고 가정합니다. 즉 사진의 노출 시간을 가중치로 하여 여러 장의 사진들을 하나로 합성합니다.

pictures with various exposures
[그림 11] 다양한 셔터스피드로 촬영한 사진. 왼쪽 위부터 시계 방향으로 15, 2.5, 1/4, 1/30초 동안 노출한 사진. 사진 출처: https://en.wikipedia.org/wiki/Multi-exposure_HDR_capture

아래의 코드(출처: OpenCV 공식 문서)를 통해 HDR 사진을 생성할 수 있습니다. 해당 코드를 실행시키려면 파이썬과 OpenCV, Numpy가 설치된 환경이 필요합니다.

# Debevec et al.의 방법을 사용해 HDR 이미지를 생성하는 코드
# 코드 출처: https://docs.opencv.org/3.4/d2/df0/tutorial_py_hdr.html
import cv2 as cv
import numpy as np

# 여러 노출로 찍은 사진들을 리스트로 불러옵니다.
img_fn = ["0.jpg", "1.jpg", "2.jpg", "3.jpg"]
img_list = [cv.imread(fn) for fn in img_fn]
exposure_times = np.array([15.0, 2.5, 0.25, 0.0333], dtype=np.float32)

# 여러 장의 노출을 합쳐 하나의 이미지를 만듭니다.
merge_debevec = cv.createMergeDebevec()
hdr_debevec = merge_debevec.process(img_list, times=exposure_times.copy())

# 만들어진 이미지에 gamma correction을 적용합니다.
# 우리의 눈이 인식하는 밝기와 실제 이미지의 픽셀 값의 크기는 선형적으로 비례하지 않습니다.
# 따라서 비선형 함수를 사용하여 우리 눈이 실제로 인식하는 밝기에 따라 이미지를 변형시킵니다.
tonemap1 = cv.createTonemap(gamma=2.2)
res_debevec = tonemap1.process(hdr_debevec.copy())

# 현재 데이터타입이 0.0~1.0 범위의 4바이트의 float이기 때문에, 0~255 범위의 1바이트 정수로 변환시킨 후 사진을 저장합니다.
# 0 이하의 값과, 1 이상의 값은 각각 0과 255로 처리됩니다.
res_debevec_8bit = np.clip(res_debevec * 255, 0, 255).astype("uint8")
cv.imwrite("ldr_debevec.jpg", res_debevec_8bit)

그 결과는 아래와 같습니다. 결과 사진이 조금 어둡게 나와서 밝기를 임의로 높이긴 했습니다만, 위 네 사진과 비교하면 하늘의 색 정보와 건물의 색 정보가 모두 그럭저럭 살아있는 것을 확인할 수 있습니다. 우리 눈으로 보는 것과 더욱 비슷해졌죠.

HDR!
[그림 12] Debevec et al.의 방법을 사용해 합성한 HDR 사진.

HDR 사진을 만들 때 한 가지 고려할 점은, 여러 장의 사진이 정확히 같은 각도로 찍혀야 이미지 합성 과정에서 결과물이 흐릿해지지 않는다는 것입니다. 이를 해결하는 접근법은 두 가지가 있는데, 하나는 파노라마에서 소개한 "호모그래피"를 사용하여 모든 사진들의 각도를 하나로 맞추는 것이고, 다른 하나는 CNN과 같은 신경망을 사용하여 하나의 사진 만으로 HDR 사진을 생성하는 것입니다(!). 두 번째 접근법이 더욱 궁금하시다면 2020년 CVPR 학회에 올라온 논문(Yu-Lun Liu et al.)을 참조해보세요.

3. Night Mode - 어두운 사진들을 빠르게 쌓아나가기

누구나 어릴 때에는 불꽃놀이를 좋아합니다. 저는 불꽃축제를 직접 눈으로 본 게 중학교 1학년 때가 처음이었는데요, 아파트 난간에 기대어 팬텍 베가 아이언 스마트폰으로 찍었던 사진이 아직도 남아 있더라고요.

Fireworks... nope
[그림 13] 2015년에 촬영한 불꽃축제.

대충 하얀 건 불꽃이고 나머지 빛들은 아파트들의 조명들인 것을 알 수 있지만, 나머지 요소들은 거의 알아볼 수 없었습니다. 스마트폰의 이런 한계는 2018년에 구글이 전례 없는 카메라 중심 스마트폰을 세상에 내놓기 전까지 계속됐어요.

iphone vs pixel: night photography
[그림 14] 아이폰 XS에서 HDR로 촬영한 야간 사진(왼쪽)과 Pixel 3의 Night Sight를 이용한 사진(오른쪽). 사진 출처: https://blog.google/products/pixel/see-light-night-sight/

2018년 11월, 구글은 Pixel 3 스마트폰을 세상에 내놓으면서 "Night Sight" 모드를 처음으로 소개했습니다. 당시의 아이폰과 비교해서 훨씬 밝고 적은 노이즈를 가진 야간 사진을 찍는 것이 가능해졌습니다.

Night Sight의 핵심이 되는 연산은 두 가지입니다.

  1. 환경에 따라 셔터 스피드 결정하기
    • 셔터 스피드는 사진을 찍을 때, 카메라의 센서가 빛을 받는 시간을 의미합니다. 셔터 스피드가 길어지면 더 밝은 사진을 찍을 수 있지만, 센서가 빛을 받는 시간이 길어지면서 사진이 흔들릴 가능성 또한 증가합니다. 우리가 밤에 찍는 사진이 항상 흔들렸던 이유입니다.
    • Night Sight는 스마트폰이 흔들리는 정도를 측정해, 사진이 흔들리지 않을 셔터 스피드를 계산합니다. 흔들림이 없는 환경이라면 긴 셔터스피드의 밝은 사진을 적게 찍고, 흔들림이 큰 환경이라면 짧은 셔터스피드의 어두운 사진을 여러 장 찍어 합성합니다. Pixel 3는 흔들림이 많을 때에는 1/15초의 사진을 15장 합성하여 1초의 노출을 가진 한 장의 사진을 만들어낼 수 있었고, 흔들림이 적을 때에는 1초의 사진을 6장 합성하여 6초의 노출을 가진 더 밝은 한 장의 사진을 만들어낼 수 있었습니다.
  2. 화이트밸런스 보정하기
    • Night Sight 기술은 기본적으로 주변 광원의 세기를 극대화하는 기술이기 때문에, 사진의 피사체가 주변 광원의 색의 영향을 크게 받습니다. 따뜻한 색의 조명 아래에서 찍은 아래의 Night Sight 사진에서도 피부의 색이 전반적으로 황색을 띠며 왜곡된 것을 볼 수 있습니다. 머신러닝 기술을 이용하여 피사체의 종류를 감지하면, 해당 피사체의 원래 색을 추론하여 광원에 의해 왜곡된 피부색을 보정할 수 있습니다. 사진에서 얼굴을 감지하여, 원래 얼굴의 자연스러운 색상에 맞춰 사진을 보정하는 것이죠. 아래 그림 처럼요.
correcting face color on night sight
[그림 15] Night Sight에서 결과물의 얼굴 색을 보정한 결과. 사진 출처: https://blog.google/products/pixel/see-light-night-sight/

여러 장을 촬영해야 하기 때문에 촬영 시간이 1~6초로 길어진다는 한계는 있지만, 가방에 있는 DSLR(만약에 있다면)을 꺼내 사진을 찍고, 그걸 또 휴대전화에 옮기는 시간보다는 훨씬 빠를 것 같습니다. :)

계산사진학의 미래

이외에도 스마트폰에 적용되는 계산사진학 기술은 정말 다양합니다. 이 글에서 소개하지 않은 멀티 카메라를 이용한 줌과 배경흐림 (DxOMark의 설명(영문)), 머신러닝을 이용해 하나의 카메라로부터 깊이 정보를 추론하는 것 등이 있습니다. 매년 삼성, 애플, 구글, 샤오미에서는 새로운 플래그십 스마트폰을 선보이며 계산사진학의 역사를 다시 쓰고 있습니다. 앞으로 계산사진학은 어떻게 발전해나갈까요? 계산사진학은 이제 사진을 잘 찍는 것을 넘어, 잘 찍은 것 같아 보이는 사진을 만들어내는 경지에 접어들었습니다. 아래 사진은 Sony a1 카메라와 50mm F1.2 GM 렌즈를 사용하여 촬영한 인물사진...이 아니라, 그렇게 프롬프트를 넣었을 때 나온 인물 사진입니다. 불쾌함의 골짜기를 이미 넘어선 것 같습니다.

Midjourney AI generated image. 50mm F1.2
[그림 16] Midjourney AI가 생성한 인물 사진. 사진 출처: https://www.sonyalpharumors.com/ai-trickery-how-to-take-a-realistic-portrait-with-a-sony-a1-and-50mm-f-1-2-gm-lens-without-to-actually-usually-using-the-gear/

또다른 화두는 요즘 화제가 되고 있는 대규모 언어 모델(LLM, Large Language Model)이 어떻게 사진을 찍고 처리하는 과정을 발전시킬 수 있을지입니다. Bing AI와 같은 언어 모델은 아래 그림과 같이 특정한 상황이 주어졌을 때 사진을 찍을 수 있는 최적의 카메라 설정값을 비교적 정확하게 추천해줍니다. 카메라의 모델명을 인식해 해당 카메라에만 존재하는 "연속 촬영: Hi" 기능을 추천해주기도 하고, 제가 사용하는 렌즈의 초점거리가 선수를 찍기에는 너무 짧다고 지적해주기도 하죠. 최신 언어 모델이 스마트폰에서도 오프라인으로 잘 실행될 수 있는 2023년1, 이런 언어 모델이 스마트폰의 카메라 앱에 통합되는 것은 시간문제일 것입니다.

Bing AI consults about soccer photography with A6000
[그림 17] 축구 경기 촬영 상황에 대한 조언을 주는 Bing AI.

스마트폰으로 좋은 사진을 찍을 수 있는 것에 모자라 세상에 존재한 적 없는 사진을 AI가 만들어낼 수 있는 오늘날의 계산사진학 기술은 필연적으로 "기술은 사진을 어디까지 조작해도 되는가?"에 대한 논쟁을 낳을 것입니다. 포토샵이 처음 출시되어 사진에서 물체를 지우고 추가할 수 있게 되었을 때, 사진가들 사이에서 사진 작품의 조작 여부를 놓고 여러 논란들이 일어났던 것처럼 말입니다(참고 기사). "사진은 세상의 본모습을 담아야 한다"는 주장과 "사진가의 의도를 강조하기 위해 사진을 편집할 수 있다"는 주장은 그때나 지금이나 앞으로나 첨예하게 맞설 것입니다.

한가지 중요한 점은, 위와 같은 논쟁에도 불구하고 계산사진학 기술은 계속 발전해나갈 예정이라는 것입니다. 그것이 우리 각자가 사진을 찍는 목적과 동기를 도울 수 있는 방향이라면, 기술에 대한 거부감은 조금 내려놓고 기술의 발전을 지켜보는 것도 나쁘지 않을 것 같습니다. 이참에 우리가 사진을 찍는 목적과 동기에 대해 다시 한 번 생각해보자구요.

읽어주셔서 감사합니다 :)

참고자료

Footnotes

  1. 구글의 최신 대용량 언어모델 PaLM 2의 가장 작은 모델 Gecko는 스마트폰에서 오프라인으로 실행할 수 있을 정도로 적은 파라미터를 가지고 있습니다. 출처: Google 블로그

버그 없는 코드를 향하여

· 약 13분
최상아 (retro)
백엔드 개발자

본 글은 KAIST 강의 중 김문주 교수님의 <소프트웨어 소스 코드 동적 분석>, 유신 교수님의 <소프트웨어 테스팅 자동화 기법>, 허기홍 교수님의 <프로그래밍 논증>을 참고하였음을 밝힙니다

돌아가기 하는 코드를 작성하는 것은 어느 때보다도 쉬워진 세상이다.

적당한 Prompt를 LLM에 입력하기만 하면 몇백줄의 코드가 뚝딱 나오고, 조금만 바꾸면 원하는 결과를 얻을 수 있는 Working Example도 수두룩하다.

그런데 그렇게 작성한 코드가 정말 바르게 작동하는 코드인지는 어떻게 알 수 있을까?

Software Verification 및 Testing과 관련된 학부 수업을 3개 수강하고 개요를 정리해보았다.

Program Verification

프로그램의 정합성을 수학적으로 증명할 수는 없을까? 프로그램의 일부 지점에서 항상 성립하는 성질(Invariant)을 찾거나 프로그램 자체를 논리식(First-order theory)으로 해석하고 Automated Theorem Prover을 이용하는 방법으로 가능하다.

Invariant 이용하기

Insertion Sort를 증명하는 방법을 되새겨보자. 루프 진입 전과 루프 내부에서 성립하는 성질을 찾은 후, 루프 종결 직후 입력 배열이 정렬되어있다는 성질을 유도하면 된다.

Insertion Sort 수도코드 (출처: CLRS)

Insertion Sort 수도코드 (출처: CLRS)

위 코드에서 Invariant는 다음 두 가지이다.

  1. 바깥쪽 루프 내부인 2번 줄에서는 이미 1번째부터 (j-1)번째 에 해당하는 원소들이 정렬되어 있다.
  2. 8번째 줄에서는 i번째부터 j번째에 해당하는 원소들이 key와 같거나 큰 값을 가지고 있다.

j=1 일때 1번 Invariant를 만족시키므로, 수학적 귀납법에 의해 모든 j에 대해 위 Invariant가 성립한다. 따라서 이 알고리즘이 종료될 때 A는 정렬된 상태가 된다.

논리식의 Satisfiability 증명하기

다음과 같은 프로그램이 crash를 호출할 가능성이 있을까?

def foo(x, y):
z = 2 * x
if y > 0:
w = 2 * y
if w + x == 0:
crash()

위 프로그램은 다음 논리식으로 표현할 수 있으므로, 이 식을 만족시키는 x, y 의 값이 존재한다면 crash를 호출하게 될 것이다.

$$ (z = 2x) \land (y > 0) \land (w=2y) \land (w+x=0) $$

Z3와 같은 SMT Solver 구현체에 아래 논리식을 입력하면 식을 만족하는 변수의 값들이 존재하는지, 혹은 만족될 수 없는 식인지 알려준다. (참고로 간단한 Boolean 변수로 이루어진 논리식의 Satisfiability를 증명하는 문제조차 NP Complete으로 알려져 있어, 효율적인 SMT Solver을 구현하는 것 자체가 또다른 연구분야이다.)

이와 같은 접근은 프로그램의 특정 지점까지 접근할 수 있는지 알아내거나, 두 프로그램의 출력을 다르게 만드는 입력의 유무 등 다양한 문제에 활용할 수 있다.

Verification의 한계

내 프로그램이 수학적으로 옳다고 증명하는 것은 매력적으로 보인다. 그러나 현실 세계의 프로그램은 너무 복잡하고 개발자들은 게으르다. 논리식으로 쉽게 분석 가능하거나 Invariant를 명시한 실용적인 소스 코드는 찾을 수가 없다. 모든 경우의 수를 고려하기 위해 Theorm Prover에 엄청난 컴퓨팅 파워가 필요한 것은 말할 필요도 없다.

Software Testing

버그 없는 소프트웨어를 향한 보다 인기있는 기법에는 테스팅이 있다. 다익스트라는 “테스팅은 버그가 있다는 것만 보여줄 뿐이지 버그가 없다는 것을 보여줄 수는 없다”고 말했지만, 엔지니어로서는 약간의 정확성을 포기하고라도 소프트웨어의 품질을 높일 수 있는 방법을 찾아야한다. 테스트를 더 잘 할 수 있는 방법을 알아보자.

CI / Automated Testing

가장 보편적고 실용적인 테크닉은 테스트를 코드베이스에 기록하고, 전체 테스트 셋의 실행을 자동화하는 것이다. CI를 달아 커밋을 할 때마다 테스트가 돌도록 하고, 모든 테스트가 통과해야 코드 리뷰를 시작할 수도 있다. 프레임워크별로 테스트 작성과 실행 자동화를 돕는 도구도 이미 여럿 존재한다.

 2022년 스택오버플로우 개발자 설문에 따르면 58% 이상의 개발자가 Automated Testing을 회사에서 도입하고 있다고 응답했다.

2022년 스택오버플로우 개발자 설문에 따르면 58% 이상의 개발자가 Automated Testing을 회사에서 도입하고 있다고 응답했다.

Metrics for Testing

좀 더 기술적으로 테스트를 개선하려면, 테스트를 얼마나 “잘” 하고 있는지에 대한 척도가 필요할 것이다.

Coverage

흔히 사용하는 척도는 Coverage로, 프로그램이 실행 가능한 모든 경우의 수를 얼마나 실행했는지 수치화한다. 전체 statement 혹은 branch를 한 번은 실행했는지 확인하는 것이 가장 쉽고, 이외에 프로그램을 그래프로 해석하여 Edge-Pair나 Prime Path Coverage를 계산할 수도 있다.

이렇게 계산한 Coverage를 높여나가는 방향으로 테스트 개선의 목표를 설정할 수도 있다. 단, 수치가 높아진다고 반드시 버그를 발견할 확률이 높아지는 것은 아니니 의도적으로 좋은 테스트를 작성하기 위한 노력을 기울여야한다.

Mutation Testing

다른 재미있는 방법은 Mutation Testing이다. 기존 코드를 약간씩 변경한 Mutant를 만들어서, 지금의 테스트들이 이 변경사항을 얼마나 잘 잡아낼 수 있는지(=얼마나 실패하는지) 평가한다. 기존 코드는 대체로 맞고, Mutant는 완전히 틀린 코드일 가능성이 높다. Mutant에 대해서 테스트가 실패한다면? 해당 테스트는 비슷한 버그를 발견 가능한 제대로 된 테스트라고 할 수 있을 것이다.

Test Case Generation

테스트 케이스를, 보다 정확히는 입력을 자동으로 생성할 수도 있다. 그러나 생성된 입력에 대해 프로그램이 어떤 행동을 보여야 하는지는 일반적으로는 알 수 없다. 개발자가 수동으로 작성해주거나, 프로그램이 정상적으로 종료되었다는 것을 확인하는 것만으로 만족해야할 수도 있다.

Random Testing

가장 간단하게는 아무 값이나 넣어보고 잘 작동하는지 확인할 수 있겠다. 이때 중요한 것은 “아무 값”을 잘 선택하는 것이다. 비슷한 입력만 잔뜩 넣어서는 리소스 대비 효과가 덜하니, 입력 간의 “거리”를 계산하여 최대한 멀리 떨어진 입력을 선택해야 버그를 발견할 확률이 높아질 것이다.

Model Checking

리소스가 무한하다면 가능한 입력을 모두 넣어볼 수도 있다. int 범위의 변수에 대해 정말 -2,147,483,648 부터 2,147,483,647 까지 넣어보는 것이다! 물론 루프나 객체가 존재하는 일반적인 프로그램의 경우 정말 모든 경우를 세는 것은 불가능하니, 루프를 몇 번만 돌고 빠져나올 것인지, 배열의 최대 길이는 얼마로 할 것인지 등을 설정하여 해당 경우만 체크한다.

Concolic Testing (Dynamic Symbolic Execution)

좀 더 똑똑한 입력 생성 방법도 있다. 일단 아무 값이나 넣어서 프로그램을 실행한 후, 실행 과정 중 계산한 Bool Expression의 결과를 뒤집는 방향으로 입력 파라미터를 조정한다. 앞서 언급한 SMT Solver을 이용하면 주어진 식을 만족시키는 입력 파라미터들을 얻을 수 있다. 모든 경우의 수를 실행했다면 더 이상의 입력 생성을 중단한다.

def foo(x, y):
if x == 1 and x < y:
return 0
elif x == 2 and x > y:
return 1
else:
return 2
ConstraintsDataNext Constraints
x = 0, y = 0not (x == 1 and x < y) and not (x == 2 and x > y)
not (x == 1 and x < y) and (x == 2 and x > y)x = 2, y = 1x == 1 and x < y
x == 1 and x < yx = 1, y = 2

Conclusion

엔지니어는 인간의 요구사항을 컴퓨터가 이해할 수 있도록 명확히 하는 것에만 집중하고, 귀찮은 코딩, 검증, 테스팅, 그리고 디버깅을 자동화할 수 있다면 얼마나 좋을까? 이 글에서는 이러한 “최종 목표”를 달성하기 위한 검증과 테스팅의 극히 일부 개념을 소개했다.

LLM을 활용한 코딩이 보편화되는 요즘, 보다 정확하고 안전한 프로그램을 만드는 것이 보다 주목 받고 상용화되었으면 한다.

동방에 누구 있나요? Wi-Fi 신호로 특정 공간에 있는 사람 확인하기

· 약 21분
권순호 (snowsuno)
안녕하세요! 2023 휠장입니당 :)

안녕하세요, SPARCS에서 개발자로 활동 중인 snowsuno 권순호입니다.

SPARCS에는 동아리 방을 이용하는 회원들의 편의를 위한 DONGBANG이라는 내부 서비스가 있습니다. 여러 가지 기능이 있지만, 그 중에서도 가장 핵심 기능은 바로 동아리 방(이하 동방)에 누가 있는지 실시간으로 알려 주는 기능입니다.

동방 앱 스크린샷

위 그림처럼 DONGBANG 서비스는 사용자의 어떠한 액션 없이도 자동으로 SPARCS 동방에 있는 사람들이 누구인지 표시해 줍니다. 어떻게 동방에 누가 있는지를 알고, 이를 표시해 주는 걸까요?

이 포스트에서는 DONGBANG 서비스, 그 중에서 특히 동방에 누가 있는지 알려주는 이 기능의 원리와 이를 구현한 과정, 그리고 그 과정에서 부딛히게 된 여러 문제들을 모두 하나하나 따라가면서 이 서비스가 어떻게 동작하는지에 대해 적어보려 합니다.

간단한 원리부터

그렇다면 그래서 DONGBANG은 어떤 원리로 사람이 동방에 있는지 알 수 있는 것일까요?

그 답은 바로 Wi-Fi에 있습니다. 동방 Wi-Fi에 연결되어 있는 디바이스를 기반으로 어떤 회원이 동방에 있는지 판단하는 것입니다. 얼핏 보면 간단해 보이지만, 이 기능을 실제 구현하여 동방에 누가 있는지를 알기 위해서는 훨씬 더 복잡한 과정이 필요합니다.

Wi-Fi에 연결된 기기 받아오기

먼저 와이파이에 연결된 기기들을 받아올 수 있는 방법이 필요하겠죠? 공유기의 종류나 제조사에 따라서 조금씩은 다르겠지만, SPARCS 동방에 있는 Wi-Fi의 경우, 관리자 API를 통해 접속 중인 기기 정보를 가져올 수 있습니다.

이를 위해 동아리 방에 raspberry pi를 설치하여 연결된 기기의 목록을 받아와 DONGBANG 서비스의 서버로 전송하도록 하였습니다. 관리자 API를 포워딩하여 외부의 서비스 서버가 직접 요청하도록 하는 방식도 가능하나, 보안적 측면과 후술할 추가적인 절차의 필요성으로 인해 동아리 방의 로컬 네트워크 망에 실제 기기를 설치하는 방법을 선택하게 되었습니다.

여기까지 오면 거의 다 왔다고 생각하시겠지만, 사실 이제부터가 시작입니다. 왜냐하면 여기에서 받아올 수 있는 정보는 기기의 (보통은 랜덤화된) MAC 주소와, 그 기기에 현재 할당되어 있는 로컬 IP 뿐이기 때문이죠. 즉, 관리자 API만으로는 연결되어 있는 기기들은 알 수 있지만, 그 기기가 누구의 것인지는 알 수 있는 방법이 없습니다.

기기 등록하기: 이 기기는 누구의 것?

DONGBANG에서는 이 문제를 '기기 등록하기'라는 절차를 넣음으로써 해결하려고 했습니다. 유저들이 자신의 기기가 무엇인지 서비스에 등록한다면 어떤 기기가 누구의 것인지 알 수 있기 때문이죠.

가장 간단하고 직관적인 방법으로는 유저가 직접 자신의 기기의 MAC 주소를 서비스에 등록하는 방법이 있습니다. 하지만 이 방법은 User Experience 측면에서도 번거롭고 MAC 주소를 직접 등록한다는 점에서 개인 정보 보호의 측면에서도 문제가 있으며, 또한 요즘 사용되는 대부분의 모바일 기기의 경우 AP마다 다른 randomized MAC을 사용하여 연결하기 때문에 유저가 이 MAC 주소를 올바르게 등록한다는 보장도 없었죠.

그래서 다음으로 생각하게 된 방법은 로컬 IP를 이용하는 방법이었습니다. Raspberry pi에 HTTP 서버를 띄워 두고, DONGBANG 클라이언트에서 기기 등록 버튼을 눌렀을 때 이 HTTP 서버의 로컬 IP로 요청을 보내도록 하는 것입니다. 이 요청에서는 서버와 클라이언트가 모두 동방 Wi-Fi의 로컬 네트워크 상에 있게 되기 때문에 서버에서 수신하게 되는 HTTP 요청의 IP 주소는 public IP가 아닌, 그 Wi-Fi의 로컬 네트워크 상에서 할당받은 local IP가 됩니다. 이러한 과정을 통해 서버에서는 사용자의 기기의 SPARCS Wi-Fi 상에서의 local IP를 알 수 있게 됩니다.

이제 필요한 정보는 모두 모였습니다. Wi-Fi의 관리자 API를 통해서 특정 기기의 MAC 주소Local IP를 알 수 있고, '기기 등록하기' 절차를 통해 사용자Local IP를 알 수 있게 되었습니다. 이 정보를 통해 DONGBANG 서비스에서는 어떠한 기기가 어떠한 사용자의 것인지를 알 수 있게 되고, 이를 바탕으로 누가 동방에 있는지 정보를 제공할 수 있게 되었습니다.

예상치 못한 문제: Local IP, HTTPS와 Mixed content policy

로컬 IP로의 요청을 활용하여 사용자의 기기 정보를 받아 오는 계획은 완벽해 보였습니다. 그러나 이를 구현하는 과정해서 사소해 보였지만 사실은 그렇지 않았던 문제와 마주치게 되었습니다. 바로 브라우저의 'Mixed content policy' 였습니다.

DONGBANG 서비스는 SSL/TLS를 사용하여, 즉 HTTPS로 서빙되는 웹 서비스입니다. 그러나, 기기 등록을 위해 사용되는 동방 로컬 네트워크의 raspberry pi HTTP 서버에 접속하기 위해서 우리는 raspberry pi의 로컬 주소를 사용하기 때문에 이는 당연히 HTTP 접속을 사용하게 됩니다.

문제는 대부분의 브라우저는 보안 상의 이유로 HTTPS 사이트에서 HTTP 주소로의 요청을 허용하지 않는다는 점입니다(Mixed content policy).

Mixed content policy

처음에는 단순히 SSL 인증서를 발급받아 Raspberry pi 서버로의 연결에도 HTTPS를 적용하면 쉽게 해결될 문제라고 생각했습니다. 192.168.0.xxx와 같은 Local IP 주소로는 SSL 인증서를 발급받을 수 없기 때문에, Raspberry pi 서버를 포트 포워딩 해준 뒤 포워딩된 외부 주소를 통해서만 SSL 인증서를 발급받을 수 있었습니다.

문제는 이렇게 되면 사용자의 기기에서 Raspberry pi 서버로 가는 요청이 Raspberry pi의 Local IP로의 요청이 아닌, 포워딩 된 외부 주소를 통한 요청이 된다는 점이었습니다.

기기 등록하기 절차의 포인트는 로컬 네트워크 상에서 사용자의 기기가 Raspberry pi 서버에 요청을 보내면, 서버가 이 요청의 IP를 알 수 있다는 점을 이용기기가 그 네트워크 상에서 할당받은 Local IP를 알아내는 것이었습니다..

SSL 인증서를 발급 받아 raspberry pi 서버에도 HTTPS를 적용하면 되지 않느냐, 라고 생각하실 수도 있겠지만, 192.168.로 시작되는 로컬 IP에는 인증서를 발급받을 수 없습니다. Raspberry pi 서버에 SSL 인증서를 발급받기 위해서는 그 서버가 listen 중인 IP/port를 forwarding하고, forwarding 한 public IP에 SSL 인증서를 발급받는 방법밖에 없는데, 이렇게 되면 raspberry pi 서버로의 요청이 local IP가 아닌 public IP로의 요청이 되는 것이기 때문에 '로컬 네트워크 상에서의 접속을 통해 기기의 local IP를 알아낸다'는 기기 등록하기 절차 자체가 불가능해지게 됩니다.

문제가 되는 부분을 정리해 보자면 다음과 같습니다:

  1. Raspberry pi로 접속할 수 있는 Public IP가 존재하지 않으면 SSL 인증서를 발급받을 수 없습니다.
  2. 포트 포워딩을 통해 Public IP를 할당하고 SSL 인증서를 발급받을 수 있지만, 이 경우 Local IP를 통한 접속이 아닌, 포워딩 된 IP를 통한 접속이 되기 때문에 Raspberry PI에서는 요청자의 Local IP가 아닌 Public IP를 알게 됩니다.
  3. Raspberry pi 서버를 통한 기기 등록하기 절차에서는 Local network 상에서의 접속을 통해 Local IP를 얻어와야 하기 때문에, 포트 포워딩을 하게 되면 기존에 계획한 메커니즘대로 동작하지 못하게 됩니다.
  4. 그러나 포트 포워딩을 하지 않게 되면 SSL 인증서를 발급받지 못하기 때문에 Mixed content policy에 부딛히게 됩니다.

이를 해결하기 위한 오랜 고민 끝에 결국 크게 두 가지의 해결 방안을 구상하게 되었습니다.

1. Super DMZ(Twin IP)

첫 번째 해결 방안은 바로 Super DMZ(Twin IP)를 이용하는 방법입니다. Super DMZ는 랜이 할당받은 Public IP를 특정 기기의 Local IP로 직접 할당해 줄 수 있는 공유기의 기능입니다. 기존의 일반적인 상황이라면 기기의 Local IP는 DHCP 서버가 할당해준 192.168.0.xxx와 같은 망 내에서만 사용 가능한 IP였겠지만, Super DMZ 기능을 활성화 하게 되면 Public IP가 바로 Local IP로 할당되기 때문에 두 주소가 같아지게 됩니다.

이 주소로 접속하는 상황에서 접속자가 서버와 다른 네트워크 상에 있다면 포트 포워딩과 거의 동일하게 동작하게 되지만, 같은 네트워크 상에서 접속하는 경우 로컬 네트워크의 DHCP 서버에서 바로 서버의 로컬 주소로 라우팅 해 주기 때문에 Local IP로 접속하는 것과 동일하게 동작하게 됩니다.

이 방법은 기존의 설계를 변경하지 않고 그대로 적용할 수 있는 솔루션이지만, 모든 포트가 expose된다는 점에서 보안 측면의 문제가 까다로워지게 됩니다. 그래서 기존의 설계를 약간 변경하지만, 이와 같은 인프라 상의 보안 정책은 변경하지 않는 두 번째 방안을 구상하게 되었습니다.

2. 별도 페이지와 리디렉트를 이용한 방법

Mixed content policy는 DONGBANG 서비스는 HTTPS 상에서 동작하나, raspberry pi에 접속하기 위한 주소는 HTTP이었기 때문에 발생하는 문제였습니다. 기존에는 raspberry pi에 접속하기 위한 주소를 HTTPS로 바꾸기 위한 방향으로 접근하고 있었지만, 더 간단하게 DONGBANG 서비스를 HTTP로만 서빙하는 방법도 있습니다. 하지만 이 기기 등록 절차만을 위해서 HTTPS 프로토콜이 주는 보안적 이점을 모두 포기하고 HTTP 서빙을 할 수는 없는 노릇입니다.

하지만 기기 등록 절차를 위한 페이지만을 따로 분리해서 이 페이지만 HTTP로 서빙한다면 어떨까요? 유저가 기기 등록 절차 페이지를 사용하는 것은 일회성, 그것도 아주 짧은 시간 동안이기 때문에 이 방법을 택하였을 때 얻게 되는 시스템의 복잡성 감소 등을 생각해 보았을 때 충분히 합리적인 선택입니다.

게다가, 이 페이지를 raspberry pi 서버를 통해 서빙한다면 HTTP로 서빙되는 기기 등록 페이지는 동방 Wi-Fi의 로컬 망에서만 접속 가능해지기 때문에 보안적인 약점 또한 어느 정도 커버 가능해집니다. 기기 등록 절차가 동아리 Wi-Fi에 연결되어 있을 때에만 이루어진다는 점을 생각해 보았을 때, 기기 등록 페이지는 동방 Wi-Fi의 로컬 망에서만 접속 가능해도 충분하기 때문이죠.

정리해보자면 다음과 같습니다. DONGBANG 서비스는 여전히 기존 서버에서 HTTPS로 작동하지만, 기기 등록 페이지는 별도의 페이지로 분리되어 동아리의 raspberry pi를 통해 동아리 Wi-Fi의 로컬 네트워크에서 HTTP로 서빙됩니다. 기기 등록 과정에서 발생하는 요청들은 모두 HTTP이기 때문에 Mixed content policy 문제가 발생하지 않고, 모두 외부로 노출되지 않고 동방 Wi-Fi의 로컬 네트워크 상에서만 일어나기 때문에 보안적인 약점도 어느 정도는 상쇄됩니다.

바뀐 설계, 그리고 사소한 문제들

결국 SPARCS 당시 Wheel장과의 논의 끝에 두 번째 방법을 선택하게 되었습니다. 기기 등록 방식이 초기 기획의, DONGBANG의 클라이언트에서 바로 API 요청을 보내는 방식에서 별도의 페이지에서 진행되는 방식으로 변경됨에 따라 사소한 문제들이 발생했습니다.

Page 간 User credentials 전달하기

페이지가 두 개로 분리되게 되면서 기기를 등록할 User credential을 전달할 방법이 필요하게 되었습니다. 기존에 페이지가 분리되지 않았을 때에는 다른 요청과 마찬가지로 유저 토큰과 함께 요청을 보내면 되었지만, 페이지가 분리되면 이 페이지는 그 유저가 누구인지도, 접근 토큰도 알 수 없기 때문에 페이지 간에 이를 전해줄 방법이 필요했습니다.

바뀐 설계에서는 유저를 기기 등록 페이지로 리디렉트해주는 방식을 사용했기 때문에 이 리디렉션 URL의 쿼리 매개변수(query parameter)에 credential 토큰을 담아 리디렉트 해 주게 되었습니다. 토큰은 scope는 기기 등록만 가능하고, 만료 기간은 1분 정도로 매우 짧게 설정된 jwt 토큰을 사용하였습니다.

기기 등록 UI를 언제 띄워야 하지?

기존의 설계에서는 1. DONGBANG 서비스에 접속 중인 기기가 등록되어 있지 않고 2. 그 기기가 동방 Wi-Fi에 접속 중일 때 기기를 바로 등록할 수 있는 카드 UI를 홈에 띄워 주었습니다. 그러나 기기 등록 방식이 바뀌면서 이가 불가능해지게 되었습니다.

따라서 새로 바뀐 설계에서는 기기가 동방 Wi-Fi에 연결되었는지 확인하기 위해 기존의 로컬 raspberry pi를 ping 해보는 방식이 아닌, 서버에서 요청의 origin이 동방의 public IP인지 확인하고, 이에 따라 해당 UI를 표시하도록 하였습니다.

긴 여정의 끝

"Wi-Fi를 이용하여 동아리 방에 누가 있는지 알아낸다"는 단순해 보이기도, 마치 마술같아 보이기도 하는 신기한 컨셉을 실제로 구현하면서 많은 문제들을 마주하게 되었습니다. 그러나 이를 해결해 나가는 과정에서 정말 많은 것을 배울 수 있었습니다.

긴 글 읽어주셔서 감사합니다. 궁금한 점이 있으시면 언제든 물어봐 주세요 :)

시간이 없을 때 백엔드 서버 띄우는 법

· 약 8분
황인준 (yuwol)
6월에 태어나서 유월입니다.

인간은 살다 보면 백엔드 개발을 해야 할 때가 찾아 오곤 한다. 그중에는 시간이 부족해 복잡하고 반복되는 작업 없이 원하는 기능을 수행하게 만들고 싶을 때가 있다. 이번 시간에는 정말 간단하게 백엔드 서버를 띄우는 방법을 보도록 하자. 최대한 best practice를 따르며 작성하겠지만 깊은 디테일은 다루지 않고 넘어갈 것이다. 우리는 바쁜 현대인이지 않은가! 글 중간중간에 도움 될 만한 레퍼런스를 적어 놓았으니 참고하도록 하자.

개발 환경 설정

개발 환경 설정 과정은 필요한 부분들만 빠르게 설명하고 실제 코드 작성하는 부분을 집중해서 보도록 하자.

작업할 디렉토리로 이동한 후 가상환경을 생성하고 필요한 패키지들을 설치한다.

$ python3 --version
Python 3.11.3

# 가상환경 생성
$ python3 -m venv .venv
$ source .venv/bin/activate

# 패키지 추가
$ python3 -m pip install \
> Django djangorestframework drf-spectacular \
> black isort \
> python-dotenv

패키지 버전 문제가 있을까 싶지만 적고 넘어가겠다. (drf-spectacular 버전을 보면 major 버전이 0인데 Release management를 보면 버전을 고정할 것을 권장하고 있다.)

Package             Version
------------------- -------
black 23.3.0
Django 4.2.1
djangorestframework 3.14.0
drf-spectacular 0.26.2
isort 5.12.0
python-dotenv 1.0.0

아래 명령으로 프로젝트를 시작하자. Django 튜토리얼을 따라 django-admin startproject mysite처럼 작성하면 mysite/mysite 같이 괴상한 구조가 생겨버리므로 프로젝트명을 무엇으로 하든지 간에 아래처럼 config로 작성하는 것이 보기에 깔끔하다. 뒤에 .을 적은 까닭은 새로운 디렉토리를 생성하지 않고 현재 경로에서 프로젝트를 진행하기 위함이다.

django-admin startproject config .

위를 실행하고 나면 아래와 같은 보일러플레이트가 생성된다.

$ tree
.
├── config
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
└── manage.py

2 directories, 6 files

아직 아무 것도 없지만 실행은 할 수 있다. 아래를 실행하고 <http://127.0.0.1:8000>에 접속하면 환영 인사와 함께 로켓 이미지를 볼 수 있다.

python3 manage.py runserver

Django: The install worked successfully! Congratulations

Settings

manage.py 파일을 보면 아래와 같은 줄이 있다. config/settings.py에서 설정한 값들을 이용해 프로젝트를 시작하기 위한 것이다. 설정 가능한 값들은 Django: Settings에서 확인 가능하다.

# manage.py

def main():
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
...

위에서 설치한 패키지 중 DRFdrf-spectacularconfig/settings.py 파일의 INSTALLED_APPS list에 추가하자. 각각에 대한 설명은 후술하겠다.

# config/settings.py

INSTALLED_APPS = [
...
"rest_framework",
"drf_spectacular",
]

아래를 실행해서 마이그레이션을 완료한다.

python3 manage.py makemigrate
python3 manage.py migrate

SECRET_KEY는 노출되면 안 되니 커밋을 하기 전에 .env 파일로 옮기고 불러오는 방식으로 수정하자.

# .env

SECRET_KEY = "django-insecure-4sg5r0@uh!=&mwm+99*zf4i5^hla1qzj!9au7+_k70@ke23**0"
# config/settings.py

import os
from dotenv import find_dotenv, load_dotenv

load_dotenv(find_dotenv())
SECRET_KEY = os.environ["SECRET_KEY"]

Models

Django 튜토리얼에서 간단한 투표 앱을 만드는 것으로 설명을 하니 비슷하게 따라가 보겠다. 이미 있는 튜토리얼 따라가서 뭐 하겠나 싶을 수 있겠지만 DRF를 사용하고 일부 추가적인 기능들을 넣는다는 점에서 의의를 두자. (mini Biseo를 만든다는 생각으로 시작해 본다.)

우선 polls라는 앱을 만드는 것으로 시작해 보자. apps 디렉토리를 생성하고 그 안에 polls 앱을 생성한다.

mkdir apps
cd apps
python3 ../manage.py startapp polls

투표 앱을 만들기 위해 세 개의 테이블이 필요하다: poll, choice, vote. Poll은 여러 개의 choice를 가지고 choice는 여러 개의 vote를 가진다.

하나의 .py 파일에는 하나의 모델만 정의하는 것이 좋으므로 models.py 파일을 삭제하고 아래처럼 models 디렉토리를 생성한다.

$ mkdir -p polls/models
$ cd polls/models
$ touch __init__.py poll.py choice.py vote.py

$ tree
.
├── __init__.py
├── choice.py
├── poll.py
└── vote.py

생성한 세 개의 .py 파일에 모델을 작성한다. 각 attribute가 하나의 DB entry가 된다. Field의 종류는 Django: Fields에서 확인 가능하다.

# apps/polls/models/poll.py

from django.db import models


class Poll(models.Model):
title = models.CharField("title", max_length=255)
text = models.TextField("text")
start_date = models.DateTimeField(
"date started",
blank=True,
null=True,
) # Created != Started
end_date = models.DateTimeField(
"date ended",
blank=True,
null=True,
)

def __str__(self) -> str:
return self.title
# apps/polls/models/choice.py

from django.db import models


class Choice(models.Model):
poll = models.ForeignKey("Poll", on_delete=models.CASCADE)
text = models.TextField("text")

def __str__(self) -> str:
return self.text
# apps/polls/models/vote.py

from django.conf import settings
from django.db import models


class Vote(models.Model):
choice = models.ForeignKey("Choice", on_delete=models.CASCADE)
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)

def __str__(self) -> str:
return f"({self.user}) -> ({self.choice})"
# apps/polls/models/__init__.py

from .choice import Choice
from .poll import Poll
from .vote import Vote

apps.py 파일에서 PollsConfig.name = "apps.polls"로 수정하고 settings.py 파일의 INSTALLED_APPS list에 "apps.polls.apps.PollsConfig"를 추가한다.

# config/settings.py

INSTALLED_APPS = [
"apps.polls.apps.PollsConfig",
...
]

마이그레이션은 다음처럼 실행한다.

python3 manage.py makemigrations polls
python3 manage.py sqlmigrate polls 0001
python3 manage.py migrate

Admin

Django의 장점은 어드민 페이지를 제공한다는 것이다. 이를 보기 위해 우선 어드민을 생성한다.

$ python3 manage.py createsuperuser
Username (leave blank to use 'yuwol'): admin
Email address: admin@example.com
Password: ********
Password (again): ********
Superuser created successfully.

서버를 시작하고 <http://127.0.0.1:8000/admin/>으로 접속하면 아래와 같은 로그인 화면을 볼 수 있다. 위에서 설정한 값들을 입력하고 들어가자.

Django Admin Sign In Page

들어가면 기본적인 페이지만 보이고 아직 아무 것도 없는 것을 확인할 수 있다.

Django Admin Default View

admin.py 파일을 수정해 위에서 생성한 세 개의 모델을 어드민 페이지에 추가할 수 있다.

# apps/polls/admin.py

from django.contrib import admin

from .models import Choice, Poll, Vote

admin.site.register(Choice)
admin.site.register(Poll)
admin.site.register(Vote)

Django Admin Added View

일단은 어드민 페이지를 가지고 놀아보자. Poll을 만들고 Choice와 Vote를 연결해 추가할 수 있다. 아직 엔드포인트를 작성하지 않았지만 간단하게 추가할 수 있다. 이는 추후 본 글을 수정하거나 새 글을 적는 방식으로 추가하겠다.

양자 키 분배(QKD)로 가는 길

· 약 14분
권영완 (phenol)
뉴아라 프론트 개발자

안녕하세요, 이 글에서는 제가 관심있는 암호학의 한 분야인 '양자 암호'에 대해 다루고자 합니다. 여기서 양자 암호(Quantum Cryptography)란 기존의 암호 체계에 양자역학의 성질을 도입한 분야로, 최근 활발히 연구되고 있습니다. 오늘은 양자 암호가 대두되기 까지의 맥락, 그리고 양자 암호 프로토콜으로 가장 대표적인 양자 키 분배(QKD)에 대해 알아보고자 합니다.

양자 컴퓨터의 발전

Google Sycamore

위 사진은 2019년 발표된 구글의 양자 컴퓨터 Sycamore 입니다. 해당 프로세서는 53 큐비트의 성능을 가지고 있으며, 구글은 이 프로세서가 기존의 슈퍼 컴퓨터로는 제시간 내에 해결할 수 없는 문제를 양자 컴퓨터로 해결할 수 있게 되는 시점인, 양자 우월성(quantum supermacy)에 도달했다고 주장하였습니다. 이후 IBM 사에서는 구글이 해결한 문제가 슈퍼 컴퓨터로 2.5일이면 해결할 수 있는 것이라고 반박하긴 했지만, 여전히 Sycamore는 양자 컴퓨터의 엄청난 발전을 보여준 사례입니다.

큐비트 (Qubit)

양자 컴퓨터는 정보의 단위로써 파동과 입자의 성질을 동시에 띠는 큐비트(qubit)를 사용합니다. 고전적인 컴퓨터가 정보의 단위로써 전류의 유무를 0/1로 나타내는 비트(bit)를 사용하는 것과 대조적으로, 양자 컴퓨터의 각각의 큐비트는 0/1 값이 중첩된 상태를 가집니다. 가장 친근한 예시는 전자(electron)로, 전자의 스핀은 업과 다운이 중첩된 상태를 가지며, 관측 시 해당 상태의 확률 진폭에 따라 한 가지 상태로 붕괴하게 됩니다. 이처럼 큐비트는 0/1 두개의 기반 상태가 중첩(superposition)된 상태를 가지며, 우리는 이러한 큐비트에 각종 논리 연산을 수행할 수 있습니다. 이로부터 고전적인 컴퓨터로는 수행할 수 없는 알고리즘을 양자 컴퓨터는 수행할 수 있게 되는 것입니다.

쇼어 알고리즘 (Shor's Algorithm)

Shor&#39;s Algorithm

위 사진은 1994년 미국의 수학자 피터 쇼어(Peter Shor)가 발표한 논문으로, 양자 컴퓨터에서 소인수 분해와 이산 로그 문제를 다항시간에 해결하는 알고리즘을 제시하였습니다. 이는 엄청난 파급력을 가지는데, 현대에 우리가 사용하는 공개 키 암호 알고리즘의 대부분이 해당 문제에 기반하기 때문입니다.

공개 키 암호는 한 방향을 계산하는 것은 쉽지만 특정 정보 없이는 역방향을 계산하는 것이 어려운 트랩도어 함수를 이용합니다. 가장 대표적인 공개 키 알고리즘인 RSA는 큰 수의 소인수 분해를, Diffie–Hellman 키 교환은 이산 로그 문제를 트랩도어 함수로써 사용하는데, 이 문제들이 양자 컴퓨터를 이용하면 다항 시간내에 해결 가능하다고 제시된 것입니다.

하지만 앞서 보았듯이 양자 컴퓨터는 현재 연구 개발 단계에 있으며, 큐비트의 수나 성능 면에서 현재의 암호체계를 해독할 수준에는 미치지 못하고 있습니다. 2021년 IBM이 쇼어 알고리즘으로 N=21을 소인수 분해하는데 성공했다고 하는데, 우리가 쓰는 RSA가 2048~4096 비트의 암호키를 사용하는 것에 비하면 매우 작은 수입니다. 따라서 대비할 시간이 주어져온 것인데, 암호학은 크게 두가지 방법으로 돌파구를 찾게 됩니다.

양자 내성 암호 (PQC)

사실 양자 컴퓨터가 소인수 분해 등 특정 문제들을 빠르게 해결할 수 있는 것이지, 모든 문제를 다항 시간에 해결할 수 있는지는 전혀 미지수입니다. 따라서 공개 키 암호의 알고리즘을 양자 컴퓨터로 효율적인 해법이 제시되지 않은 문제들을 이용하면 되는데, 그 예시로 다변수 기반 암호, 격자 기반 암호 등이 존재합니다. 이는 기존의 공개 키 암호 프로토콜에서 알고리즘을 변경하는 방식이기에, 현재 실현 가능성이 높다고 평가 받으며 활발히 연구되고 있습니다.

양자 암호 (QC)

그 외에 굉장히 흥미로운 가능성이 제시되었는데, 바로 암호 통신 자체에 양자역학의 성질을 띠는 입자를 사용하는 것입니다. 그렇게 되면 양자역학에서의 '파동함수의 상태를 관측하면 하나의 기반 상태로 붕괴한다'는 성질을 잘 이용해 통신의 중간에 발생하는 도청을 감지할 수 있습니다. 또한 계산 복잡도가 아닌 물리적 원리에 기반하기 때문에, 무조건적 보안성이 수학적으로 증명 가능합니다. 여기서 무조건적 보안성(unconditional security)란 공격자에게 어떠한 컴퓨팅 자원이나 시간에 대한 제약이 존재하지 않을 때의 보안성을 말합니다. 이러한 양자 암호의 가장 대표적인 프로토콜이 바로 양자 키 분배(QKD)입니다.

양자 키 분배 (Quantum Key Distribution, QKD)

QKD는 두 명의 참가자 Alice와 Bob이 양자 통신 채널을 통해 하나의 키를 안전하게 공유하는 것을 목표로 합니다. 그 후 공유한 키를 이용해 OTP(One-time pad) 방식으로 평문을 암호화해 일반 채널으로 통신합니다.

OTP는 비밀 키를 공유하는 경우 무조건적 보안성을 가지는데, 이것이 이때까지 사용되지 못한 이유는 비밀 키를 안전하게 공유할 수 있다면 굳이 암호화 하지 않고 평문을 안전하게 공유하면 되기 때문입니다. 하지만 QKD를 이용하면 양자 채널에서 키를 랜덤하게 생성하고, 이를 안전하게 공유할 수 있게 됩니다. 이러한 QKD의 가능성을 제시해준 것이 바로 1984년 Charles Bennett과 Gilles Brassard가 제시한 BB84 프로토콜인데, 이에 대해 살펴보겠습니다.

BB84

BB84 protocol

  1. Alice가 두 개의 편광판에서 가능한 4개의 상태 (수평 |0⟩, 수직 |1⟩, 대각선 |+⟩, 반대각선 |-⟩) 중 하나를 무작위로 골라 편광된 빛을 Bob에게 보낸다.
  2. Bob은 두 개의 편광판 (직선 기저 |0⟩, |1⟩ 또는 대각선 기저 |+⟩, |-⟩) 중 하나를 무작위로 골라 빛을 관측한다.
  3. 이를 반복한 뒤, Alice와 Bob은 각각의 시행에서 사용한 편광판의 정보를 공개한다.
  4. Alice와 Bob이 같은 편광판을 선택한 경우의 관측값만을 남겨 이를 암호 키로 사용한다.

BB84 protocol 2

여기서 중요할 점은, Alice와 Bob이 같은 편광판을 이용하는 경우 이 둘의 관측값은 동일하지만 다른 편광판을 이용하는 경우 기반 상태가 바뀌면서 완전히 랜덤한 관측값을 얻게 된다는 것입니다. 따라서 둘이 선택한 편광판을 공개하면 실제 관측값을 공개하지 않고도 Alice와 Bob은 서로의 관측값이 동일한 비트만을 남길 수 있습니다.

도청을 감지할 수 있다

Alice와 Bob의 송수신 과정에서 Eve가 도청을 시도하여, 해당 빛을 Bob 보다 먼저 관측한다고 가정해봅시다. 이 경우 양자 상태를 완벽하게 복제할 수 없다는 복제 불가능성 정리(no-cloning theorem)에 의해 Eve는 새로운 상태의 빛을 Bob에게 보내야 합니다. 하지만 Eve는 Alice가 초기에 선택한 상태가 자신의 관측값과 일치하는지 알 수 없기 때문에, Alice와 다른 초기 상태로 빛을 송신하는 경우가 확률적으로 발생합니다. 그러면 Bob이 Alice와 같은 편광판을 선택했음에도 관측 결과가 다른 경우가 생기게 됩니다. 따라서 Alice와 Bob이 적당한 길이의 관측 결과를 비교하여 오차율을 측정함으로써 도청자의 존재 유무를 파악할 수 있습니다.

맺음말

우리는 양자 컴퓨터와 양자 알고리즘의 발전에 따른 기존 암호학의 문제를 살펴보았고, 이에 대한 해법으로 양자 내성 암호(PQC)와 양자 암호(QC)가 제시되기 까지의 과정을 살펴보았습니다. 또한 대표적인 양자 암호 프로토콜인 양자 키 분배(QKD)를 BB84라는 간단하고도 강력한 예시를 통해 알 수 있었습니다. 사실, 기술적인 구현의 한계와 실제 상황에서의 보안에 대한 위험 등을 이유로 미국 국가안보국(NSA)와 영국 국립사이버보안센터(NCSC)는 현재 QKD 보다 PQC를 해법으로 사용하기를 권장하고 있습니다. QKD는 분명 상용화되기 까지 많은 연구와 검증이 필요할 것입니다. 그럼에도 불구하고, 수학적으로 증명 가능한 완전한 보안 체계를 만든다는 것만으로 QKD는 굉장히 매력적이라고 생각합니다.

읽어주셔서 감사드립니다 :)

참고 자료

  1. 구글 Sycamore
    • Arute, F., Arya, K., Babbush, R. et al. Quantum supremacy using a programmable superconducting processor. Nature 574, 505–510 (2019).
  2. 쇼어 알고리즘
    • P. W. Shor, "Algorithms for quantum computation: discrete logarithms and factoring," Proceedings 35th Annual Symposium on Foundations of Computer Science, Santa Fe, NM, USA, 1994, pp. 124-134.
    • IBM N=21 소인수분해: Skosana, U., Tame, M. Demonstration of Shor’s factoring algorithm for N = 21 on IBM quantum processors. Sci Rep 11, 16599 (2021).
  3. BB84
    • C. H. Bennett, G. Brassard et al., "Quantum cryptography: Public key distribution and coin tossing," in Proceedings of IEEE International Conference on Computers, Systems and Signal Processing, vol. 175, no. 0. New York, 1984.
  4. QKD 보고서

코드 리뷰와 리팩토링: 좋은 코드를 작성하는 방법

· 약 7분
김건 (suwon)
곧 카이스트 졸업할 몸

안녕하세요. Taxi팀의 PM으로 활동하고 있는 김건(suwon)입니다.

소프트웨어 개발은 복잡한 과정입니다. 프로젝트의 성공은 코드의 품질에 크게 의존하며, 이를 달성하는 방법 중 하나는 코드 리뷰와 리팩토링입니다. 좋은 코드를 작성하는 방법, 코드 리뷰의 중요성, 그리고 리팩토링 기법에 대해 다루겠습니다.

좋은 코드란?

좋은 코드를 작성하는 것은 간단해 보이지만, 많은 연습과 지식이 필요합니다. 좋은 코드는 효율적이고, 이해하기 쉬워야 합니다. 이를 위한 몇 가지 방법은 다음과 같습니다:

  • 명확한 명명법: 변수, 함수, 클래스의 이름은 그것이 무엇을 하는지 명확하게 나타내야 합니다. 예를 들어, calculateAverage()는 그 함수가 평균을 계산한다는 것을 명확하게 보여줍니다.

  • 단순함: 코드는 간결하고 단순해야 합니다. 복잡한 코드는 이해하기 어렵고, 오류를 유발할 가능성이 높습니다.

  • 주석: 주석은 코드의 작동 방식을 설명하고, 복잡한 로직을 이해하는 데 도움을 줍니다. 하지만 너무 많은 주석은 코드를 복잡하게 만들 수 있으므로 적절한 균형이 필요합니다.

좋은 코드로 효율성과 통일성을 높이기 위해 Taxi 팀에서는 모든 팀원들이 공통적으로 지켜야 하는 Code Rules와 Convention을 지정하고 Issue & PR 템플릿을 사용하고 있습니다. 규칙을 지키며 코드를 작성하는 것은 개인적으로 개발에 많은 시간을 요구할 수 있지만, 거시적으로 팀 전체의 개발 속도는 감소시킬 것입니다. PM으로서 Code Rules를 팀원 전체에게 강요하는 이유입니다.

image-20230514221254055

코드 리뷰의 중요성

코드 리뷰는 개발자가 작성한 코드를 동료가 검토하고 피드백을 제공하는 과정입니다. 이는 여러 가지 이유로 중요합니다:

  • 버그 발견: 코드 리뷰는 코드에서 버그를 찾는 데 도움이 됩니다. 개발자가 작성한 코드에는 그 개발자가 인지하지 못한 버그가 있을 수 있습니다.

  • 코드 품질 향상: 코드 리뷰는 코드의 품질을 향상시키는 데 도움이 됩니다. 리뷰어는 개선할 수 있는 부분을 지적하고, 더 나은 방법을 제안할 수 있습니다.

  • 지식 공유: 코드 리뷰는 개발 팀 내에서 지식을 공유하는 좋은 방법입니다. 다른 사람의 코드를 리뷰하면서 새로운 기법이나 관점을 배울 수 있습니다.

팀원들의 성장과 코드의 품질을 위해 Taxi 팀에서는 코드 리뷰를 권장하고 있습니다. 코드 리뷰를 통해 서비스에서 발생할 수 있는 많은 버그를 발견하였고 저 개인적으로도 팀원들의 코드를 리뷰하면서 많은 지식을 얻을 수 있었습니다.

image-20230514221254056

리팩토링 기법

리팩토링은 코드의 구조를 개선하면서 외부적으로 보이는 행동은 그대로 유지하는 것을 말합니다. 이는 코드의 가독성을 향상시키고, 유지 보수를 용이하게 하는데 도움이 됩니다. 주요 리팩토링 기법으로는 다음과 같은 것들이 있습니다:

  • Extract Function(함수 추출): 긴 함수나 복잡한 코드 조각을 별도의 함수로 분리하는 기법입니다. 이를 통해 코드의 가독성을 향상시키고, 재사용성을 높일 수 있습니다.

  • Rename Variable(변수 이름 변경): 변수의 이름을 더 명확하게 변경하는 것입니다. 이를 통해 코드의 의도를 더 잘 전달하고 이해하는 데 도움이 됩니다.

  • Replace Magic Number with Symbolic Constant(매직 넘버를 상징적 상수로 변경): 코드 내에 특정 값을 직접 사용하는 대신 의미 있는 상수로 대체하는 기법입니다. 이를 통해 코드의 가독성을 높이고, 오류를 방지할 수 있습니다.

  • Remove Dead Code(불필요한 코드 제거): 더 이상 사용되지 않는 코드를 제거하는 기법입니다. 이를 통해 코드의 복잡성을 줄이고, 유지 관리를 용이하게 할 수 있습니다.

결론

좋은 코드를 작성하는 것은 소프트웨어 개발의 핵심적인 부분입니다. 코드 리뷰와 리팩토링은 이를 달성하는 데 중요한 도구입니다. 코드 리뷰를 통해 팀원들과 지식을 공유하고, 서로의 코드를 향상시킬 수 있습니다. 또한, 리팩토링을 통해 코드의 품질을 개선하고, 유지 보수를 용이하게 할 수 있습니다. 이러한 절차를 통해, 우리는 더 나은 소프트웨어와 서비스를 만들어낼 수 있습니다.

Swagger와 함께하는 🤘Swag🤘 넘치는 문서화

· 약 14분
최동원 (won)
won 많이 벌고 싶어요

안녕하세요! SPARCS Taxi팀에서 백엔드 개발자로 활동중인 won입니다.

이번 글에서는 REST 웹 서비스 문서화를 위한 오픈 소스 소프트웨어 프레임워크인 Swagger에 대해 알아보고, Taxi 팀에서 Swagger를 어떻게 사용하는지에 대해 간단히 소개해보려 합니다.

서론

평소와 다름없이 열심히 개발을 하던 중 어떤 함수나 type에 대한 정보가 얻고 싶을 때, 블로그와 구글링 다음 최후의 수단으로 공식 문서를 읽어보곤 합니다. 처음에는 영어로 적힌데다가 불편함이 이만저만이 아니지만, 익숙해진다면 확실한 정보를 얻기에 이만한 방법이 없다는 걸 항상 깨닫곤 하지요.

이처럼 코드 문서화는 코드가 어떻게 작동하고, 어떻게 활용되는지 파악할 때 매우 유용하게 작용합니다. 하지만 개발을 하는 개발자의 입장에서 문서화는 새로운 코드를 짜거나 혹은 기존 코드를 변경할 때마다 매번 업데이트 해줘야 하는 귀찮은 존재인데요. 어떻게 하면 문서화를 더 편하고, 더 멋지게! 할 수 있을까요?

🤘 Swagger - API 문서 자동화

image

Swagger는 개발자가 REST API를 설계, 빌드, 문서화 및 사용하는데 있어 도움이 되는 OpenAPI 사양을 중심으로 구축 된 오픈 소스 도구 세트입니다. Swagger를 사용하는 가장 큰 특징은 빠르고 쉽게 알록달록한 문서를 만들 수 있으며, 문서 화면에서 사용자가 바로 API를 테스트 해볼 수 있다는 점입니다. 원래 API가 제대로 작동하는지 사용자가 확인하기 위해서는 코드를 받아와 테스트 코드를 작성하거나 별도의 작업이 필요했지만, Swagger에서는 유저가 직접 값을 넣어보며 다양한 테스트를 진행할 수 있다는 점에서 매우 유용합니다.

개인적으로 생각하는 Swagger의 장단점을 정리하면 다음과 같습니다.

  • 장점
    • 적용하기 쉽다.
    • API 테스트를 위한 UI를 제공한다.
    • 다양한 색을 사용하여 API들을 한 눈에 파악하기 좋다
  • 단점
    • 코드에 어노테이션을 달거나, 별도의 코드를 작성해야 한다.
    • 문서만으로 유연하게 동기화 시키기는 힘들다.

❓ Swagger 어떻게 사용하나요?

지금부터는 간단하게 Swagger를 어떻게 적용하는지 살펴보고, Swagger로 작성된 예시를 Taxi팀에서 사용했던 실제 코드로 살펴보도록 하겠습니다.

개발 환경

이 글은 Node.js(express) + Javascript를 기반으로 작성되었습니다.

먼저, Swagger을 사용하기 위한 npm 모듈을 설치해줍니다.

npm install swagger-ui-express swagger-jsdoc

그 후, 다음과 같이 router 설정을 수정합니다. 이러면 /docs에 들어가면 Swagger로 작성된 문서를 확인할 수 있습니다.

// app.js
app.use("/docs", require("./src/route/docs"));

아래 코드는 swaggerUi를 사용해서 기초 Ui를 만들고, swaggerSpec에서 정의한 관련 세팅을 설정해주는 코드입니다.

  • definition에서는 버전이나 제목, 태그 같은 기본적인 정보들을 설정해 줄 수 있습니다.
  • apis에서는 Swagger annotation들이 담긴 파일들의 경로를 지정해줍니다.
// /src/route/docs.js
const swaggerSpec = swaggereJsdoc({
definition: {
openapi: "3.0.3",
info: {
title: "Taxi API Document",
version: "1.0.0",
},
basePath: "/",
tags: [
{
name: "logininfo",
description: "로그인 정보 제공",
},
],
consumes: ["application/json"],
produces: ["application/json"],
},
apis: ["src/route/*.js"],
});

router.use(swaggerUi.serve);
router.use(swaggerUi.setup(swaggerSpec, { explorer: true }));

이렇게 설정이 완료되었다면 각 api들에 대한 정보를 @swagger annotation을 통해 작성해주면 됩니다. 아래 코드는 로그인된 사용자의 정보를 반환하는 API를 Swagger로 문서화한 예시입니다.

// /src/route/logininfo.js
/**
* @swagger
* /logininfo:
* get:
* tags: [logininfo]
* summary: 사용자 정보 반환
* description: 로그인되어 있는 사용자의 정보를 반환
* responses:
* 200:
* description: 사용자의 로그인 세션이 유효한 경우, 현재 로그인된 사용자의 정보를 반환, <br/>
* 세션이 유효하지 않은 경우, 빈 오브젝트를 반환
* content:
* application/json:
* schema:
* type: object
* properties:
* id:
* type: string
* description: 사용자 id
* sid:
* type: string
* description: 사용자 sid
* name:
* type: string
* description: 사용자 이름
*/
router.route("/").get(logininfoHandlers.logininfoHandler);

이렇게 annotation을 잘 작성해주고, 아까 설정한 주소를 접속하면 멋지게 정리된 저희의 문서들을 볼 수 있습니다!

img

또한, 각 API들을 직접 테스트한 결과를 다음과 같이 웹 화면에서 직접 확인할 수 있습니다.

img

Swagger에 대해 더 자세하게 알고 싶다면 Swagger 공식문서를 읽어보는 것을 추천드립니다.

🚕 Swagger in Taxi

최근 이벤트로 뜨거운 관심을 받고 있는 Taxi 서비스는 이전까지 github Readme를 사용해서 문서화를 진행해왔는데요, 이번 23년도 봄학기부터 PM(@suwon)님의 제안으로 문서 최신화 작업과 더불어 Swagger를 통한 문서 자동화 작업을 진행 중에 있습니다. 지금부터는 Taxi 팀에서 Swagger을 어떻게 사용하고 있는지 간단하게 알아보겠습니다.

Swagger 문서들만 파일로 따로 관리하자!

위의 예시처럼 annotation을 통해 Swagger을 작성할 수도 있지만, 이 방법의 경우 복잡한 API에서 annotation이 너무 길어지게 되어 코드가 자칫 더러워질 수 있다는 위험이 있습니다. 또한 문서에서 비슷한 부분이 계속 반복되는 경우 나중에 변경이 필요할 때 다 수정해야하는 번거로움이 생기겠죠.

그렇기 때문에 저희는 각 문서들을 json 형식의 object로 관리하여 이 문제를 해결하였습니다. 아래 코드와 같이 각 파일에서 작성된 Swagger 문서들을 가져와 각 문서들을 마치 변수처럼 활용하는 것을 볼 수 있습니다. 이렇게 하면 깔끔하고 좀 더 편하게 문서를 작성할 수 있습니다.

참고로 Swagger는 .yaml 형식과 .json 형태 모두 작성 가능하기 때문에 편한 양식에 맞춰 작성하면 되겠습니다.

// /src/routes/docs/swaggerDocs.js
const reportsSchema = require("./reportsSchema");
const reportsDocs = require("./reports");
const logininfoDocs = require("./logininfo");
const locationsDocs = require("./locations");

const swaggerDocs = {
openapi: "3.0.3",
info: {
title: "Taxi API Document",
version: "1.0.0",
},
basePath: "/",
tags: [
{
name: "locations",
description: "출발지/도착지 정보 제공",
},
{
name: "logininfo",
description: "로그인 정보 제공",
},
{
name: "reports",
description: "사용자 신고 및 신고 기록 조회",
},
],
consumes: ["application/json"],
produces: ["application/json"],
paths: {
...reportsDocs,
...logininfoDocs,
...locationsDocs,
},
components: {
schemas: {
...reportsSchema,
},
},
};

module.exports = swaggerDocs;

문서화와 Validation을 한번에!

Back-End에서 유효하지 않은 요청을 처리하고 싶지 않은 경우 Validator을 통해서 이를 처리해줍니다. 만약 Validator에서 설정해준 형식이 아니라면 중간에 error을 보내서 예상하지 못한 코드 동작이 일어나지 못하도록 방지해 줄 수 있겠죠.

Taxi팀 또한 express-validator라는 모듈을 통해 유효성 검사를 진행해왔는데요. 여기서 문제는 express-validator 에서도 각 parameter들의 type을 정의하고, Swagger 문서에서도 schemas를 통해 2중으로 API parameter들의 type을 정의해줘야 했다는 점입니다. 문서화도 가뜩이나 귀찮은데 API가 변할 때 마다 validator 설정도 다시 해줘야 한다면 매우 번거로울 것입니다.

이 문제를 저희는 AJV(another json validator)을 사용하여 해결하였습니다! AJV는 Node.js / 브라우저를 위한 JSON Validator를 생성해주는 라이브러리로, JSON 형식으로 작성된 Schema를 함수로 컴파일하여 유효성 검사를 진행하는 라이브러리입니다.

아까 언급하였듯, Taxi에서는 Swagger 문서를 JSON 형식으로 작성하기 때문에 Swagger의 schemas에 정의된 스키마들을 자연스럽게 AJV에서 사용할 수 있습니다. 즉, express-validator을 사용하지 않고도 JSON 형식으로 정의된 문서를 사용하여 유효성을 검사할 수 있는 것이지요.

이를 적용한 결과, 아래와 같이 type이 유효하지 않은 bad-type 요청에 대하여 error을 내는 것을 확인할 수 있습니다.

image

🍃 Spring REST Docs - Swagger말고 다른건 없나요?

image Spring REST Docs는 Swagger와 더불어 API 문서 자동화에 자주 사용되는 Tool입니다. Swagger와 달리 별도의 타이핑이 필요 없으며, Test를 통과해야만 문서가 생성된다는 특징이 있습니다. 이러한 특징 때문에 Spring REST Docs는 다음과 같은 장단점을 갖고 있습니다.

  • 장점
    • 실제 코드에 추가되는 코드가 없다.
    • 테스트가 성공해야 문서가 작성되어 API의 신뢰도를 높여준다.
    • 버전 변화에 유연하고 정확성이 높다.
  • 단점
    • 테스트 코드를 따로 작성해야 하며 별도로 준비할 것이 많다
    • 적용하기 어렵다...!

이 글에서 더 자세하게 다루지는 않겠지만, 관심 있는 분들은 이에 관해 자세하게 작성된 아래의 글들을 참고하면 좋을 것 같습니다.

글을 마치며

23년 5월 현재에도 Swagger 문서화 작업은 계속 진행 중이며, https://taxi.sparcs.org/docs/ 에서 저희가 지금까지 작업한 Swagger 문서를 확인할 수 있습니다. 아직 완벽하지 않지만 점점 추가될 저희 API 문서들을 기대해 주세요!

또한 위에서 언급한 코드들은 저희 taxi-back 레포에 잘 정리되어 있으니 관심 있는 분들은 코드를 살펴보시는 것을 추천합니다.

지금까지 README를 통해서만 Document를 관리해왔다면 한 번 Swagger을 통해 Documentation을 더 Swag 넘치게 만들어 보는 것은 어떨까요? Swagger을 사용함에 있어 이 글이 도움이 되길 바라며 마무리 하겠습니다.

긴 글 읽어주셔서 감사합니다 :)

Sessions vs JWT

· 약 15분
김효경 (diana)
SPARCS를 사랑하는 개발 새내기

Sessions vs JWT 입문 (동영상)

세션 vs 토큰 vs 쿠키? 기초개념 잡아드림. 10분 순삭!

Cookies

토큰 vs 쿠키? 는 틀린 질문

쿠키를 이용해서 서버는 사용자의 브라우저에 데이터를 저장한다.

작동 원리

서버에 request를 보내고 받은 response에 cookies가 있을 수 있다.

그렇게 해서 저장해 놓은 cookies를 서버에 request 보낼 때마다 같이 보낸다.

특징

  • 쿠키는 도메인에 따라 제한이 된다.
  • 쿠키는 (서버가 정한 기간에 따라) 유통기한이 있다.
  • 쿠키는 인증 정보뿐만 아니라, 여러 정보를 저장할 수 있다.
    • e.g., 웹사이트 언어 설정을 바꾸면, 서버는 쿠키를 주어서 사용자의 언어 설정을 저장한다. 이 후에, 쿠키는 request와 함께 서버로 보내지고, 서버는 언어 설정에 해당하는 response를 줄 수 있다.
  • 공간 제약이 있다.

세션과 토큰이 필요한 이유

HTTP (웹사이트를 이용할 때 쓰는 프로토콜)은 stateless

서버로 가는 모든 request가 이전 request와 독립적으로 다뤄진다. 즉, 요청이 끝나면, 서버는 그 요청에 대한 정보는 기억하지 못한다.

→ 요청을 보낼 때마다, 사용자에 대한 정보를 알려주어야 한다.

Sessions

작동 과정

  1. 유저명과 비밀번호를 서버에 보낸다
  2. 비밀번호가 맞다면 Session DB에 user 정보를 저장한다.
  3. 각 session마다 별도의 ID가 있고, 이를 쿠키를 통해 브라우저로 돌아와 쿠키에 저장된다.
  4. request를 보낼 때 session ID를 쿠키에 담아 보내게 된다.
  5. 세션 ID를 세션 DB에서 확인한다.
  6. 유저 정보에 해당하는 response를 준다.

특징

  • 중요한 유저 정보는 모두 서버가 가지고 있다.
    • 모든 request에 대해서 쿠키를 확인하여, DB를 찾고 저장하는 작업을 서버가 해야한다. 즉, user가 늘어나면 DB 리소스가 더 필요하다.
  • 쿠키가 세션 ID를 전달하기 위한 매개체 역할을 한다.
  • 세션을 이용하여 안드로이드. iOS를 만들 수 있지만, 쿠키는 사용할 수 없다. → 쿠키 대신 토큰을 사용한다.

장점

  • 다양한 기능을 추가할 수 있다.
    • e.g., 특정 유저를 쫓아내고 싶을 때
    • e.g., 원하지 않는 디바이스에서 강제 로그아웃
    • e.g., 로그인한 계정 개수를 알 수 있고, 제한할 수 있다. (넷플릭스)

단점

  • DB를 사고, 유지해야 한다.
  • 유저가 늘어날 수록 DB도 커진다.
  • 주로 redis를 이용한다.
    • 해당 목적을 수행하기 위한, 빠르고, 저렴한 DB

JWT

토큰 형식

작동과정

  1. 유저명과 비밀번호를 서버에 보낸다.
  2. 유저명과 비밀번호가 맞다면 서버는 ‘사인된 정보’(JWT)를 string 형태로 클라이언트에 보낸다.
  3. request를 보낼 때 ‘사인된 정보’(JWT)를 서버에 보낸다.
  4. 서버는 토큰을 받으면, 해당 사인이 유효한지 체크한다.

특징

  • 세션 DB가 필요없다.
  • 길이에 제약이 없다.
  • JWT는 암호화되지 않았다.

장점

  • 서버가 많은 정보를 저장하고 있다.

단점

  • 다양한 기능을 할 수는 없다.
    • e.g., 강제 로그아웃 X (해당 토큰이 만료되기 전까지 유효)

JWT 입문 (동영상)

JWT 대충 쓰면 님들 코딩인생 끝남

JWT token

JWT(JSON Web Token)에 대해서... :: Outsider's Dev Story

JWT의 구조

JOSE 헤더

JWT Claim Set

Signature

앞에서 보았듯이 JOSE 헤더와 JWT Claim Set은 암호화를 한 것이 아니라 단순히 JSON문자열을 base64로 인코딩한 것뿐이다. 그래서 누구나 이 값을 다시 디코딩하면 JSON에 어떤 내용이 들어있는지 확인할 수 있다. 토큰을 사용하는 경우 이 토큰을 다른 사람이 위변조할 수 없어야 하므로 JOSE 헤더와 JWT Claim Set가 위변조되었는지를 검증하기 위한 부분이 Signature 부분이다. JOSE 헤더와 JWT Claim Set는 JOSE 헤더와 JWT Claim Set를 base64로 인코딩해서 만든 두 값을 마침표(. )로 이어 붙이고 JOSE 헤더에서 alg 로 지정한 알고리즘 HS256  즉, HMAC SHA-256으로 인코딩하면 JWT 토큰의 세 번째 부분인 Signature를 만든다.

💡 추가 설명 > JWT(Json Web Token) 알아가기 > header를 디코딩한 값 + “.” + payload를 디코딩한 값을 위처럼 합치고 이를 your-256-bit-secret, 즉, 서버가 가지고 있는 개인키를 가지고 암호화한 것이 Signature입니다. 따라서 signature는 서버에 있는 개인키로만 암호화를 풀 수 있으므로 다른 클라이언트는 임의로 Signature를 복호화할 수 없습니다.

JWT Claim Set

JWT의 구조를 설명하기 위해서 앞에서는 직접 토큰을 만들었지만 실제로 사용하게 되면 언어별로 있는 JWT 라이브러리를 사용해서 토큰을 만들 것이므로 이 과정을 직접 수행하는 경우는 별로 없다. JWT 사이트에 언어별 추천 라이브러리가 있고 현재 지원상황까지 표시되어 있으므로 비교해서 사용하면 된다. 이런 라이브러리는 취약점에 대한 대처가 중요하므로 세부 내용을 자세히 아는 것이 아니라면 그냥 여기 나와 있는 라이브러리를 사용하기를 추천한다. 이전에 작성한 글에서는 Node.js JWT 라이브러리로 jwt-simple을 사용했지만, 개발이 거의 진행되고 있지 않아서 지금은 jsonwebtoken으로 갈아탔다.

JWT에서 토큰의 정보를 클레임이라고 부르기 때문에 이 정보를 모두 가지고 있는 바디 부분을 Claim Set이라고 부르고 Claim Set은 키 부분인 Claim Name과 값 부분인 Claim Value의 여러 쌍으로 이루어져 있다. Claim Name으로 사용 가능한 값에는 3가지 분류가 있는데 등록된 클레임 이름(Registered), 공개 클레임 이름(Public), 비밀 클레임 이름(Private)이다. 등록된 클레임 이름은 IANA JSON Web Token Claims에 등록된 이름이고 필수값은 아니지만 공통으로 사용하기 위한 기본값이 정해져 있다. 아래 목록이 등록된 클레임 이름인데 모두 선택사항이다.

  • iss: 토큰을 발급한 발급자(Issuer)
  • sub: Claim의 주제(Subject)로 토큰이 갖는 문맥을 의미한다.
  • aud: 이 토큰을 사용할 수신자(Audience)
  • exp: 만료시간(Expiration Time)은 만료시간이 지난 토큰은 거절해야 한다.
  • nbf: Not Before의 의미로 이 시간 이전에는 토큰을 처리하지 않아야 함을 의미한다.
  • iat: 토큰이 발급된 시간(Issued At)
  • jti: JWT ID로 토큰에 대한 식별자이다.

공개된 클레임이름은 토큰에서 사용하기 위해서 정의했지만, 충돌을 방지하기 위해서 공개된 이름이고 비밀 클레임이름은 서버와 클라이언트가 협의로 사용하는 이름을 의미한다.

JWT의 사용

JWT가 다른 토큰하고 가장 다른 부분은 토큰 자체가 데이터를 가지고 있다는 점이다. API 서버를 직접 구현한 적이 많지는 않지만, 일반적으로 토큰 기반의 인증을 구현한다면 API 요청 시 헤더나 파라미터에 엑세스토큰을 가져오도록 하고 이 토큰을 보고 인증한다.(서비스에 따라서 앱 ID나 비밀키를 같이 사용하기도 하지만 여기서는 JWT 범주가 아니므로 여기서는 엑세스토큰만 얘기한다.)

일반적인 토큰의 흐름을 생각한다면 API 요청 시에 들어온 토큰을 보고 이 토큰이 유효한지 확인하게 된다. 보통은 데이터베이스에 토큰을 저장해 놓고 만료시간이나 토큰의 사용자 등을 저장해 놓고 유효한 토큰인지 등을 검사하고 유효한 경우 해당 사용자라고 인식하고 이 사용자의 권한으로 사용할 수 있는 정보를 조회하게 된다. 요청마다 데이터베이스를 조회하는 것은 비용이 꽤 크므로 캐시서버 등을 두어 성능을 높이기도 한다.

Auth0 - jwt token을 만든 단체

Application Session Management

django에서 session 구현

Django Tutorial Part 8: User authentication and permissions - Learn web development | MDN

모바일 앱에서 Sessions vs JWT

세션이란? (feat.모바일 앱에서 세션)

모바일 앱에서 세션 관리

결론부터 말하면, 모바일 환경에서도 세션과 유사한 방식을 사용할 수 있지만, 잘 사용하지는 않는다.HTTP 프로토콜을 사용하면, 결국 논리성의 연속성을 유지하기 위해서 세션과 비슷한 개념이 모바일에도 필요하다. 하지만, 세션이나 쿠키와 같은 방식을 주로 사용하지는 않는 것으로 알고 있다.

모바일과의 네트워킹에서 브라우저에서의 세션과 다른 방식이 사용되는 이유는 다음과 같다.

1. 정보 유지 방식이 다르다

웹에서는 세션을 발급 받으면, 이를 쿠키에 넣어두고 다시 서버와 소통할 때 사용한다.하지만, 앱은 그 자체로 파일 시스템에 엑세스 할 수 있을 뿐 아니라, 내부 DB도 가질 수 있기 때문에 쿠키를 통해 통신할 필요가 없다.서버에서 받은 유저 식별 데이터를 따로 저장해서 통신할 때 사용하면 된다. 이 값은 보통 앱이 종료되고 재실행 돼도, 로그아웃하지 않는 이상 지워지거나 갱신되지 않는다.즉, 동작 방식은 동일하지만, 세션처럼 관리될 필요는 없는 것이다.TTL을 따로 설정할 필요가 없고, 세션같이 종료시에 expire 될 필요도 없다.또한 앱에서는 모든 정보를 서버에게 요청하는 것이 아니라, 많은 양 클라에서 가지고 있고 서버에서 받은 내용도 캐싱 되는 경우가 대부분이기 때문에, 논리적 연결성 또한 기본적으로 성립되어 있다.

2. 보안 수준

브라우저에서는 기본적으로 경로가 주소에 공개되어 있고, 환경 자체가 쿠키 값을 파악하거나 중간 탈취할 가능성이 크기 때문에, 세션을 사용하는 것이 필요하다.하지만 앱은 기본적으로 사용자와 1대1 매칭되며, 정보 탈취 가능성이 낮기 때문에 해당 방식과 유사한 방법을 사용한다.

※ 단, 웹뷰등으로 구현하는 하이브리드 앱에서는 세션 관리가 필요하다.