OAuth 2.0 및 OpenID Connect로 클라우드 기반 애플리케이션 보호하기
OAuth 2.0 및 OpenID Connect로 클라우드 애플리케이션을 보호하는 완벽한 가이드와 인증 및 인가를 통해 훌륭한 사용자 경험을 제공하는 방법.
소개
클라우드 기반 애플리케이션은 현재의 트렌드입니다. 애플리케이션 유형은 웹, 모바일, 데스크탑 등 다양하지만 모두 스토리지, 컴퓨팅 및 데이터베이스와 같은 서비스를 제공하는 클라우드 백엔드를 가지고 있습니다. 대부분의 경우, 이 애플리케이션은 사용자 인증과 특정 리소스에 대한 접근 권한을 부여해야 합니다.
직접 개발한 인증 및 인가 메커니즘도 가능하지만, 클라우드 애플리케이션 개발 시 보안은 주요 우려 사항 중 하나가 되었습니다. 다행히도, 우리의 산업에서는 OAuth 2.0 및 OpenID Connect와 같은 표준을 통해 안전한 인증 및 인가를 구현할 수 있습니다.
이 글은 다음의 가정을 하고 있습니다:
- 애플리케이션 개발(웹, 모바일 또는 기타 유형)에 대한 기본적인 이해가 있습니다.
- 인증 및 인가의 개념에 대해 들어본 적이 있습니다.
- OAuth 2.0 및 OpenID Connect에 대해 들어본 적이 있습니다.
네, '들어본 적이 있다'가 2와 3에 충분합니다. 이 글에서는 실제 예제를 사용하여 개념을 설명하고 다이어그램으로 프로세스를 설명할 것입니다. 시작해봅시다!
OAuth 2.0 vs. OpenID Connect
OAuth 2.0과 OpenID Connect에 익숙하다면 이 섹션에서 실제 예제를 다룰 것이므로 계속 읽어도 괜찮습니다. 이 표준에 대해 잘 모르는 경우에도 간단하게 소개할 것이므 로 계속 읽어도 안전합니다.
OAuth 2.0
OAuth 2.0은 사용자를 대신하여 혹은 애플리케이션 자체로서 다른 애플리케이션의 보호된 리소스에 제한된 접근을 받을 수 있게 해주는 인가 프레임워크입니다. Google, Facebook, GitHub 등 인기 있는 서비스 대부분은 OAuth 2.0을 소셜 로그인에 사용합니다 (예: "Google로 로그인").
예를 들어, 당신은 사용자의 Google Drive에 접근하고자 하는 웹 애플리케이션 MyApp을 가지고 있습니다. 사용자에게 Google Drive 자격 증명을 공유하도록 요청하는 대신, MyApp은 OAuth 2.0을 사용하여 사용자를 대신해 Google Drive 접근을 요청할 수 있습니다. 다음은 간단한 흐름입니다:
이 흐름에서, MyApp은 사용자의 Google Drive 자격 증명을 절대 보지 않습니다. 대신, Google로부터 사용자를 대신하여 Google Drive에 접근할 수 있는 액세스 토큰을 받습니다.
OAuth 2.0 용어로, MyApp은 client이고, Google은 간단하게 authorization server와 resource server입니다. 실제로는 단일 로그온(SSO) 경험을 제공하기 위해 별도의 인가 서버와 리소스 서버를 자주 갖고 있습니다. 예를 들어, Google은 인가 서버이며 Google Drive, Gmail, YouTube와 같은 여러 리소스 서버를 보유할 수 있습니다.
실제 인가 흐름은 이보다 더 복잡합니다. OAuth 2.0은 다양한 grant type, scope 및 인식할만한 다른 개념이 있습니다. 이제는 이것을 잠시 제쳐두고 OpenID Connect로 넘어갑시다.
OpenID Connect (OIDC)
OAuth 2.0은 인가에 좋지만 사용자를 식별할 방법 (즉, 인증)이 없습니다. OpenID Connect는 OAuth 2.0 상단에 추가된 인증 기능을 갖춘 ID 레이어입니다.
앞선 예시에서 MyApp은 인가 흐름을 시작하기 전에 사용자가 누구인지 알아야 합니다. 여기에는 두 사용자가 포함되어 있습니다: MyApp 사용자와 Google Drive 사용자입니다. 이 경우, MyApp은 자체 애플리케이션의 사용자를 알고 있어야 합니다.
사용자가 MyApp에 사용자 이름과 비밀번호로 로그인할 수 있다고 가정한 간단한 예를 살펴보겠습니다:
우리가 자체 애플리케이션의 사용자를 인증하고 있기 때문에, Google이 OAuth 2.0 흐름에서 했던 것처럼 보통 권한을 물을 필요가 없습니다. 그동안 사용자를 식별할 수 있는 것이 필요합니다. OpenID Connect는 ID 토큰 및 userinfo 엔드포인트와 같은 개념을 도입하여 이를 도와줍니다.
**identity provider (IdP)**는 흐름에서 새로운 독립된 참가자로 보일 수 있습니다. 이는 OAuth 2.0의 authorization server와 동일하지만 사용자 인증 및 아이덴티티 관리를 담당하는 것을 더 명확히 하기 위해 IdP라는 용어를 사용합니다.
비즈니스가 성장하면 동일한 사용자 데이터베이스를 공유하는 여러 애플리케이션을 가질 수 있습니다. OAuth 2.0과 마찬가지로 OpenID Connect를 사용하면 동일한 인가 서버를 통해 여러 애플리케이션에서 사용자를 인증할 수 있습니다. 사용자가 한 애플리케이션에 이미 로그인한 경우, 다른 애플리케이션이 그들을 IdP로 리다이렉트하면 다시 자격 증명을 입력할 필요가 없습니다. 이 흐름은 사용자 상호작용 없이 자동으로 수행될 수 있습니다. 이를 단일 로그온(SSO)이라고 합니다.
다시 한번, 이것은 매우 간소화된 흐름이며 더 많은 세부사항이 숨어 있습니다. 정보 과부하를 방지하기 위해 다음 섹션으로 넘어갑시다.
애플리케이션 유형
앞서 우리는 웹 애플리케이션을 예로 들었지만 세상은 그것보다 더 다양합니다. 아이덴티티 제공자에게 있어, 사용하는 정확한 프로그래밍 언어, 프레임워크, 플랫폼은 중요하지 않습 니다. 실제로 주목할 차이점 중 하나는 애플리케이션이 public client인지 private (trusted) client인지 여부입니다:
- Public client: 자격 증명을 비공개로 유지할 수 없는 클라이언트로, 리소스 소유자(사용자)가 이에 접근할 수 있습니다. 예를 들어, 브라우저에서 실행되는 웹 애플리케이션(예: 단일 페이지 애플리케이션).
- Private client: 리소스 소유자(사용자)에게 노출되지 않고 자격 증명을 비공개로 유지할 수 있는 클라이언트입니다. 예를 들어 서버에서 실행되는 웹 애플리케이션(예: 서버 측 웹 애플리케이션) 또는 API 서비스.
이 점을 이해한 상태에서, 다른 애플리케이션 유형에서 OAuth 2.0 및 OpenID Connect를 어떻게 사용할 수 있는지 보겠습니다.
이 글의 맥락에서는 "애플리케이션"과 "클라이언트"를 서로 교환하여 사용할 수 있습니다.
서버에서 실행되는 웹 애플리케이션
애플리케이션이 서버에서 실행되며 HTML 페이지를 사용자에게 제공합니다. Express.js, Django, Ruby on Rails와 같은 인기 있는 웹 프레임워크가 이 범주에 속합니다; 그리고 Next.js와 Nuxt.js와 같은 프론트엔드용 백엔드(BFF) 프레임워크도 포함됩니다. 이들 애플리케이션은 다음과 같은 특성을 가집니다:
- 서버는 비공개 접근만을 허용하기 때문에(대중 사용자가 서버의 코드나 자격 증명을 볼 방법이 없음) private client로 간주됩니다.
- 전체적인 사용자 인증 흐름은 "OpenID Connect" 섹션에서 논의한 것과 동일합니다.
- 애플리케이션은 ID 제공자(즉, OpenID Connect 제공자)가 발행한 ID 토큰을 사용하여 사용자를 식별하고 사용자 개인화된 콘텐츠를 표시할 수 있습니다.
- 애플리케이션 보안을 유지하려면, 애플리케이션은 대개 사용자 인증 및 토큰 획득을 위해 authorization code flow를 사용합니다.
한편, 애플리케이션은 마이크로서비스 아키텍처의 다른 내부 API 서비스에 접근하거나, 단일 애플리케이션에서 애플리케이션의 다른 부분에 대한 접근 제어가 필요할 수 있습니다. 이는 "API 보호" 섹션에서 논의할 것입니다.
단일 페이지 애플리케이션(SPAs)
애플리케이션이 사용자의 브라우저에서 실행되고 서버와 API로 통신합니다. React, Angular, Vue.js는 SPAs를 구축하기 위한 인기 있는 프레임워크입니다. 이 애플리케이션은 다음과 같은 특성을 가집니다:
- 애플리케이션의 코드가 공공에 가시적이기 때문에 public client로 간주됩니다.
- 전체적인 사용자 인증 흐름은 "OpenID Connect" 섹션에서 논의한 것과 동일합니다.
- 애플리케이션은 ID 제공자(즉, OpenID Connect 제공자)가 발행한 ID 토큰을 사용하여 사용자를 식별하고 사용자 개인화된 콘텐츠를 표시할 수 있습니다.
- 애플리케이션 보안을 유지하려면, 애플리케이션은 사용자 인증 및 토큰 획득을 위해 대개 **authorization code flow with PKCE(Plox Key for Code Exchange)**를 사용합니다.
보통 SPAs는 데이터 검색 및 업데이트를 위해 다른 API 서비스에 접근해야 합니다. 이는 "API 보호" 섹션에서 논의할 것입니다.
모바일 애플리케이션
애플리케이션이 모바일 장치(iOS, Android 등)에서 실행되고 서버와 API로 통신합니다. 대부분의 경우, 이 애플리케이션은 SPAs와 동일한 특성을 가집니다.
기계 간 (M2M) 애플리케이션
Machine-to-machine 애플리케이션은 다른 서버와 통신하는 서버(기계)에서 실행되는 클라이언트입니다. 이 애플리케이션은 다음과 같은 특성을 가집니다:
- 서버에서 실행되는 웹 애플리케이션처럼, M2M 애플리케이션은 private client입니다.
- 애플리케이션은 사용자를 식별할 필요가 없을 수 있습니다. 대신 다른 서비스에 접근하기 위해 스스로를 인증해야 합니다.
- 애플리케이션은 ID 제공자(즉, OAuth 2.0 제공자)가 발행한 액세스 토큰을 사용하여 다른 서비스에 접근할 수 있습니다.
- 애플리케이션 보안을 유지하려면, 애플리케이션은 대개 액세스 토큰을 얻기 위해 client credentials flow를 사용합니다.
다른 서비스에 접근할 때, 애플리케이션은 요청 헤더에 액세스 토큰을 제공할 필요가 있을 수 있습니다. 이는 "API 보호" 섹션에서 논의할 것입니다.
사물인터넷(IoT) 장치에서 실행되는 애플리케이션
애플리케이션이 스마트 홈 장치, 웨어러블 등과 같이 IoT 장치에서 실행되고 서버와 API로 통신합니다. 이 애플리케이션은 다음과 같은 특성을 가집니다:
- 장치의 능력에 따라 public 또는 private client가 될 수 있습니다.
- 장치의 능력에 따라 "OpenID Connect" 섹션에서 논의한 것과 다른 인증 흐름을 가질 수 있습니다. 예를 들어, 일부 장치는 사용자가 자격 증명을 입력할 수 있는 화면이 없을 수 있습니다.
- 장치가 사용자를 식별할 필요가 없는 경우, ID 토큰 또는 userinfo 엔드포인트를 사용할 필요가 없을 수 있습니다. 대신 기계 간(M2M) 애플리케이션으로 취급될 수 있습니다.
- 애플리케이션을 안전하게 유지하기 위해, 애플리케이션은 사용자 인증 및 토큰 획득을 위해 authorization code flow with PKCE(Plox Key for Code Exchange) 또는 액세스 토큰을 얻기 위한 client credentials flow를 사용할 수 있습니다.
서버와 통신할 때, 장치는 요청 헤더에 액세스 토큰을 제공할 필요가 있을 수 있습니다. 이는 "API 보호" 섹션에서 논의할 것입니다.
API 보호
OpenID Connect를 사용하면, ID 토큰 또는 userinfo 엔드포인트를 통해 사용자를 식별하고 사용자 개인화된 데이터를 가져올 수 있습니다. 이 과정은 인증이라 불립니다. 그러나 모든 인증된 사용자에게 모든 리소스를 노출하고 싶지 않은 것이 일반적입니다. 예를 들어, 관리자만 사용자 관리 페이지에 접근할 수 있습니다.
이때 인가의 개념이 필요합니다. 기억하세요, OAuth 2.0은 인가 프레임워크이며, OpenID Connect는 OAuth 2.0 상의 ID 레이어입니다; 즉, OpenID Connect가 이미 있는 경우에도 OAuth 2.0을 사용할 수 있다는 의미입니다.
"OAuth 2.0" 섹션에서 사용한 예를 다시 상기해봅시다: MyApp이 사용자의 Google Drive에 접근하길 원합니다. MyApp이 사용자의 Google Drive의 모든 파일에 접근하도록 하는 것은 실용적이지 않습니다. 대신, MyApp은 명시적으로 무엇을 접근하고자 하는지를 요청해야 합니다 (예: 특정 폴더 내의 파일에 대한 읽기 전용 접근). OAuth 2.0 용어로, 이는 scope라 불립니다.
OAuth 2.0의 맥락에서 기술적이지 않은 사용자에게 때때로 "scope"가 혼동될 수 있으므로, "permission"이라는 용어가 "scope"와 교환되어 사용될 수 있습니다.
사용자가 MyApp에 접근을 허용할 때, 인가 서버는 요청된 scope와 함께 액세스 토큰을 발행합니다. 액세스 토큰은 이후 리소스 서버(Google Drive)로 보내져 사용자의 파일에 접근하게 됩니다.
자연스럽게, 우리는 Google Drive 대신 우리의 자체 API 서비스를 사용할 수 있습니다. 예를 들어, MyApp이 사용자의 OrderService에 접근하여 사용자의 주문 이력을 가져와야 하는 경우입니다. 이번에는 사용자 인증이 이미 ID 제공자에 의해 완료되었으며 MyApp과 OrderService가 모두 우리의 제어 하에 있기 때문에, 사용자가 접근을 허용할 필요 없이 MyApp이 직접 ID 제공자가 발행한 액세스 토큰으로 OrderService에 요청을 보낼 수 있습니다.
액세스 토큰에는 사용자가 자신의 주문 이력을 읽을 수 있음을 나타내는 read:order
scope가 포함될 수 있습니다.
이제 사용자가 실수로 관리자 페이지 URL을 브라우저에 입력했다고 가정해 봅시다. 사용자가 관리자가 아니기 때문에 액세스 토큰에 admin
scope가 포함되어 있지 않습니다. OrderService는 요청을 거부하고 오류 메시지를 반환합니다.
이 경우, OrderService는 403 Forbidden 상태 코드를 반환하여 사용자가 관리자 페이지에 접근할 권한이 없음을 나타낼 수 있습니다.
기계 간 (M2M) 애플리케이션의 경우, 프로세스에는 사용자가 포함되지 않습니다. 애플리케이션은 ID 제공자로부터 직접 액세스 토큰을 요청하고 이를 사용하여 다른 서비스에 접근할 수 있습니다. 동일한 개념이 적용됩니다: 액세스 토큰은 리소스에 접근할 수 있는 필수적인 scope를 포함합니다.
인가 설계
API 서비스를 보호하기 위한 인가를 설계할 때 두 가지 중요한 사항을 고려해야 합니다:
- Scopes: 클라이언트가 접근할 수 있는 것을 정의합니다. 요구 사항에 따라 scope는 세분화될 수 있습니다 (예:
read:order
,write:order
) 또는 더 일반적일 수 있습니다 (예:order
). - Access control: 특정 scope를 가질 수 있는 사람을 정의합니다. 예를 들어, 관리자만이
admin
scope를 가질 수 있습니다.
액세스 제어와 관련하여, 일부 인기 있는 접근 방식은 다음과 같습니다:
- 역할 기반 접근 제어 (RBAC): 사용자가 무엇을 접근할 수 있는지를 정의하고 역할을 사용자에게 할당합니다. 예를 들어, 관리자 역할은 관리자 페이지에 접근할 수 있습니다.
- 속성 기반 접근 제어 (ABAC): 속성 (예: 사용자의 부서, 위치 등)을 기반으로 정책을 정의하고 이러한 속성을 기반으로 접근 제어 결정을 내립니다. 예를 들어, "엔지니어링" 부서의 사용자가 엔지니어링 페이지에 접근할 수 있습니다.
두 접근 방식 모두, 접근 제어를 검증하는 표준 방법은 역할이나 속성이 아니라 액세스 토큰의 scope를 확인하는 것입니다. 역할과 속성은 매우 동적일 수 있으며 scope는 더 정적이기 때문에 관리하기가 훨씬 더 쉽습니다.
접근 제어에 대한 자세한 정보는 RBAC와 ABAC: 알아야 할 접근 제어 모델에서 참조할 수 있습니다.
액세스 토큰
"액세스 토큰"이라는 용어를 여러 번 언급했지만, 하나를 얻는 방법에 대해서는 논의하지 않았습니다. OAuth 2.0에서는 액세스 토큰이 성공적인 인가 흐름 후 인가 서버 (아이덴티티 제공자)에 의해 발행됩니다.
Google Drive 예제를 통해 조금 더 자세히 살펴보겠습니다. 인가 코드 흐름을 사용한다고 가정해보겠습니다:
액세스 토큰 발행 흐름의 중요한 단계는 다음과 같습니다:
- 단계 2 (Google로 리다이렉트): MyApp은 인가 요청과 함께 사용자에게 Google로 리다이렉션합니다. 일반적으로 이 요청에는 다음 정보가 포함됩니다:
- 어느 클라이언트 (MyApp)가 요청을 시작하는지
- MyApp이 요청하는 scope
- 인가가 완료된 후 Google이 사용자를 어디로 리다이렉션해야 하는지
- 단계 5 (Google Drive에 접근 권한 요청): Google이 사용자에게 MyApp에 접근을 허용할지를 요청합니다. 사용자는 접근을 허용할지 거부할지를 선택할 수 있습니다. 이 단계는 **동의(consent)**라고 불립니다.
- 단계 7 (인가 데이터와 함께 MyApp으로 리다이렉트): 이 단계는 다이어그램에 새로 도입되었습니다. 액세스 토큰을 직접 반환하는 대신, Google은 MyApp에 일회성 인가 코드를 반환하여 더 안전한 교환을 제공합니다. 이 코드는 액세스 토큰을 얻기 위해 사용됩니다.
- 단계 8 (인가 코드를 사용하여 액세스 토큰으로 교환): 이것은 또한 새로운 단계입니다. MyApp은 인가 코드를 Google로 전송하여 액세스 토큰을 얻습니다. 아이덴티티 제공자로서, Google은 요청의 문맥을 구성하고 액세스 토큰을 발행할지를 결정합니다:
- 클라이언트 (MyApp)는 자신이 주장하는 존재입니다
- 사용자는 클라이언트에 접근을 허용하였습니다
- 사용자는 자신이 주장하는 사람입니다
- 사용자는 필요한 scope를 가집니다
- 인가 코드는 유효하며 만료되지 않았습니다
이 예제는 인가 서버 (아이덴티티 제공자)와 리소스 서버가 동일한 경우(Google)를 가정합니다. 만약 분리되어 있다면, MyApp과 OrderService와 같은 상황에서 흐름은 다음과 같을 것입니다:
이 흐름에서, 인가 서버 (IdP)는 MyApp에 ID 토큰과 액세스 토큰을 모두 발행합니다 (단계 8). ID 토큰은 사용자를 식별하는 데 사용되고, 액세스 토큰은 OrderService와 같은 다른 서비스에 접근하는 데 사용됩니다. MyApp과 OrderService가 모두 첫 파티 서비스이기 때문에, 일반적으로 사용자가 접근을 허용할 필요는 없으며, 대신 아이덴티티 제공자의 액세스 제어에 따라 사용자가 리소스에 접근할 수 있을지 (즉, 액세스 토큰에 필요한 scope가 포함되어 있는지) 결정합니다.
마지막으로, 기계 간 (M2M) 애플리케이션에서 액세스 토큰이 어떻게 사용되는지 살펴보겠습니다. 프로세스에 사용자가 포함되지 않고 애플리케이션이 신뢰할 수 있기 때문에, 아이덴티티 제공자로부터 직접 액세스 토큰을 요청할 수 있습니다:
접근 제어는 여전히 적용될 수 있습니다. OrderService에 있어서는, 데이터를 요청하는 사용자가 누구이거나 어떤 애플리케이션이 요청하든 중요하지 않습니다; 그것은 오직 액세스 토큰과 그에 포함된 scope를 중요하게 여깁니다.
액세스 토큰은 보통 JSON Web Token (JWT)으로 인코딩됩니다. JWT에 대해 더 알아보려면 What is JSON Web Token (JWT)?를 참조하십시오.
리소스 지시자
OAuth 2.0은 접근 제어를 위해 scope의 개념을 도입했습니다. 그러나 scope만으로는 충분하지 않다는 것을 빠르게 깨달을 수 있습니다:
- OpenID Connect는
openid
,offline_access
,profile
과 같은 표준 scope를 정의합니다. 이러한 표준 scope를 사용자 정의 scope와 혼합하게 되면 혼란스러울 수 있습니다. - 동일한 scope 이름을 공유하지만 의미가 다른 여러 API 서비스가 있을 수 있습니다.
일반적인 솔루션은 리소스(API 서비스)를 나타내기 위해 scope 이름에 접미사(또는 접두사)를 추가하는 것입니다. 예를 들어, read:order
과 read:product
는 read
와 read
보다 명확합니다. OAuth 2.0 확장 RFC 8707은 클라이언트가 접근하고자 하는 리소스 서버를 나타내기 위한 새로운 매개변수 resource
를 도입합니다.
실제로 API 서비스는 보통 URL로 정의되기 때문에, resource indicators로 URL을 사용하는 것이 더 자연스럽습니다. 예를 들어, OrderService API는 다음과 같이 표현될 수 있습니다:
보시다시피, 이 매개변수는 인가 요청 및 액세스 토큰에서 실제 리소스 URL을 사용하는 편리함을 제공해 줍니다. RFC 8707이 모든 아이덴티티 제공자에 의해 구현된 것은 아닐 수 있음을 언급할 가치가 있습니다. 아이덴티티 제공자의 문서를 확인하여 resource
매개변수를 지원하는지 확인해야 합니다(Logto는 이를 지원합니다).
간단히 말해, resource
매개변수는 클라이언트가 접근하고자 하는 리소스를 나타내기 위해 인가 요청 및 액세스 토큰에서 사용될 수 있습니다.
리소스 지시자의 접근성에 제한은 없습니다. 즉, 리소스 지시자는 API 서비스로 연결되는 실제 URL일 필요가 없습니다. 따라서 인가 과정에서의 역할을 적절히 반영하고 있습니다. 보호하고자 하는 리소스를 나타내기 위해 가상 URL을 사용할 수 있습니다. 예를 들어, 단일 애플리케이션에서 관리자만 접근할 수 있는 리소스를 나타내기 위해 가상 URL
https://api.example.com/admin
을 정의할 수 있습니다.
전체 통합하기
이제까지, 우리는 OAuth 2.0과 OpenID Connect의 기본 사항을 다루었고, 이를 다양한 애플리케이션 유형과 시나리오에서 어떻게 사용할 수 있는지 설명했습니다. 우리가 별도로 논의했지만, 비즈니스 요구 사항에 따라 이를 결합할 수 있습니다. 전체 아키텍처는 아래와 같이 간단할 수 있습니다:
아니면 조금 더 복잡할 수도 있습니다:
애플리케이션이 성장함에 따라, 아이덴티티 제공자(IdP)가 아키텍처에서 주도적인 역할을 한다는 것을 알게 될 것입니다. 그러나 이는 비즈니스 목표와 직접 관련이 없습니다. 신뢰할 수 있는 벤더에게 그것을 넘기는 것이 좋은 생각인 반면, 우리는 신중히 아이덴티티 제공자를 선택해야 합니다. 좋은 아이덴티티 제공자는 프로세스를 크게 단순화하고 개발 노력을 줄이며 잠재적인 보안 문제로부터 당신을 보호할 수 있습니다.
마무리
현대 클라우드 애플리케이션에 있어, 아이덴티티 제공자(혹은 인가 서버)는 사용자 인증, 아이덴티티 관리, 접근 제어를 위한 중심 장소입니다. 이 글에서는 많은 개념을 논의했지만, 이와 같은 시스템을 구현할 때 고려해야 할 많은 미묘한 차이가 여전히 남아 있습니다. 더 많은 것을 배우고 싶다면, 우리의 블로그에서 더 심층적인 글을 탐색할 수 있습니다.