이 장에서는 OAuth 2와 OpenID Connect 프로토콜에 대한 개요와 이들이 제공하는 인증 및 인가 시스템의 주요 요소를 다룹니다. 이들 프로토콜은 보안과 원활한 인증을 제공하는 산업 표준으로, 애플리케이션과 시스템 간 인증 책임을 분리하여 인증 과정을 간소화하고 보안을 강화합니다.
핵심 개념:
- OAuth 2와 OpenID Connect
- OAuth 2는 사용자 인증과 권한 부여를 위한 표준 프로토콜로, 인증 책임을 별도의 인증 서버에 위임하여 여러 앱에서 인증을 공유할 수 있게 합니다.
- OpenID Connect는 OAuth 2의 확장이며, 사용자의 ID 정보를 인증할 수 있도록 지원합니다. 이 프로토콜을 사용하면, 다른 앱에서 사용자가 이미 가진 자격 증명으로 로그인할 수 있도록 해 줍니다.
- 여러 앱에서의 인증 문제
- 여러 애플리케이션을 사용할 때, 각각의 앱에 대해 별도의 인증을 해야 한다면 사용자는 여러 자격 증명을 기억하고 관리해야 하므로 불편하고 복잡해집니다.
- OAuth 2를 활용하면 단일 로그인 (SSO) 방식을 구현하여, 사용자가 한 번만 로그인하고 여러 앱을 사용할 수 있습니다.
- OAuth 2의 이점
- 인증을 별도의 서버에서 관리함으로써 인증 구현 비용을 줄이고, 사용자가 여러 자격 증명을 관리하는 번거로움을 없앨 수 있습니다.
- 외부 인증 제공자 (예: 구글, 페이스북, GitHub 등)와 연동하여, 사용자에게 기존 자격 증명으로 인증을 제공할 수 있습니다. 이를 통해 앱은 인증 기능을 구현할 필요 없이 신뢰할 수 있는 인증을 받을 수 있습니다.
- OAuth 2의 시스템 구성 요소
- 사용자: 인증을 요청하는 최종 사용자.
- 클라이언트: 사용자의 인증을 요청하는 애플리케이션.
- 인가 서버: 인증을 담당하고 액세스 토큰을 발급하는 서버.
- 리소스 서버: 보호된 자원을 관리하며, 액세스 토큰을 검증하고 요청을 처리하는 서버.
- 토큰
- 액세스 토큰은 클라이언트가 리소스 서버에 요청할 때 사용하는 인증 키입니다. OAuth 2 시스템에서는 다양한 종류의 토큰이 사용되며, 각 토큰은 특정 용도와 사용 상황에 맞게 활용됩니다.
- 잠재적 위험 요소
- OAuth 2를 구현할 때 보안상 취약점이나 잘못된 구현으로 인한 위험 요소가 존재할 수 있습니다. 예를 들어, 토큰의 안전한 관리나 취약한 토큰 그랜트 유형 사용 등이 문제가 될 수 있습니다.
결론
이 장은 OAuth 2와 OpenID Connect의 복잡한 개념을 쉽게 풀어내어, 애플리케이션에 이러한 프로토콜을 어떻게 효과적으로 통합할 수 있는지에 대한 기초를 제공합니다. 이후 14장부터 16장에서는 구체적인 설정 방법과 사례를 통해 실용적인 구현 방법을 다룰 것입니다.
13.1 OAuth 2와 OpenID Connect의 큰 그림
이 섹션은 OAuth 2와 OpenID Connect가 어떻게 작동하는지, 이들 프로토콜의 역할을 쉽게 이해할 수 있도록 설명합니다. 이를 통해 사용자가 어떻게 인증을 받고 자원에 접근할 수 있는지에 대한 개요를 제공합니다.
비유: 사무실 건물에 접근하기
OAuth 2 시스템을 이해하는 데 유용한 비유는 대기업의 사무실에 들어가는 과정입니다. 회의에 참석하려면 건물에 들어가기 전에 신분증으로 본인을 인증받고, 이를 바탕으로 액세스 카드를 받아 특정 구역에 접근하게 됩니다. 이 과정에서 인증(자기 자신을 증명하는 것)과 인가(특정 자원에 접근할 수 있는 권한 부여)는 중요한 요소로 작용합니다.
OAuth 2 시스템의 주요 역할
OAuth 2 시스템에서의 역할은 마치 사무실에 들어가는 과정에서 중요한 역할을 하는 인물들과 유사합니다. 이 시스템의 주요 역할은 다음과 같습니다:
- 사용자(User): 애플리케이션을 사용하는 사람으로, 사용자는 OAuth 2 시스템 내에서 항상 존재하는 것은 아닙니다. 예를 들어, 특정 클라이언트 자격 증명 그랜트 유형에서는 사용자 역할이 없을 수도 있습니다.
- 클라이언트(Client): 사용자가 요청을 처리하기 위해 인증 및 인가가 필요한 애플리케이션입니다. 이는 웹 애플리케이션, 모바일 애플리케이션, 데스크톱 애플리케이션, 혹은 별도의 백엔드 서비스일 수 있습니다. 클라이언트는 사용자를 대신해 리소스 서버에 접근합니다.
- 리소스 서버(Resource Server): 클라이언트 애플리케이션의 요청을 인증하고 처리하는 백엔드 서버로, 실제 자원(예: 데이터베이스)을 관리하고 있습니다. 리소스 서버는 클라이언트가 보낸 요청을 처리하려면 액세스 토큰을 확인하고 유효성 검사를 합니다.
- 인가 서버(Authorization Server): 사용자 인증과 자격 증명의 안전한 관리를 담당하는 서버입니다. 인가 서버는 클라이언트가 요청한 토큰을 발급하고 관리합니다. 이 서버는 액세스 토큰을 발급하거나 새로 고치는 작업을 수행합니다.
OAuth 2 인증 및 인가 과정
OAuth 2 시스템의 인증 및 인가는 다음과 같은 단계로 이루어집니다:
- 사용자가 클라이언트 애플리케이션을 사용하려고 합니다.
- 클라이언트는 사용자에게 필요한 리소스에 접근하기 위해 리소스 서버를 호출할 수 있는 권한을 얻기 위해, 인가 서버에서 액세스 토큰을 요청합니다.
- 인가 서버는 클라이언트가 유효한 자격 증명을 제공했음을 확인한 후 액세스 토큰을 발급합니다.
- 클라이언트는 받은 액세스 토큰을 사용해 리소스 서버에 요청을 보냅니다.
- 리소스 서버는 액세스 토큰을 확인한 후 요청을 처리하고 결과를 클라이언트에게 반환합니다.
- 클라이언트는 이 결과를 사용자에게 표시합니다.
액세스 토큰
액세스 토큰은 클라이언트가 리소스 서버에 요청을 보낼 때 사용하는 인증 키입니다. 이 토큰은 클라이언트와 사용자의 인증을 증명하는 임의의 데이터 조각(대개 문자열)으로, 요청을 승인받기 위해 필요한 정보를 제공합니다. 액세스 토큰은 유효 기간이 짧고, 만료된 후에는 클라이언트가 다시 새로운 토큰을 요청해야 합니다.
OAuth 2에서의 다양한 토큰 흐름 (그랜트 타입)
OAuth 2는 다양한 그랜트 타입(grant types)을 정의하여 클라이언트가 액세스 토큰을 얻는 여러 가지 방법을 설명합니다. 이 과정은 13.3절에서 더욱 자세히 다뤄질 것입니다.
결론
OAuth 2 시스템은 클라이언트와 서버 간의 인증과 인가를 효율적으로 처리하는 표준 프로토콜입니다. 이 장에서는 OAuth 2와 OpenID Connect의 큰 그림을 제시하고, 그들이 어떻게 상호작용하는지에 대해 설명했습니다. 이후 장들에서는 OAuth 2를 실제로 어떻게 구현할지에 대한 구체적인 설명이 이어질 것입니다.
13.2 다양한 토큰 구현 사용
OAuth 2에서 토큰은 인증 및 인가 프로세스의 핵심 요소입니다. 클라이언트는 이 토큰을 사용하여 백엔드(리소스 서버)에서 특정 자원에 접근할 수 있는 권한을 받습니다. 이 섹션에서는 토큰 유형과 각 유형이 OAuth 2 인증 및 인가 프로세스에서 어떻게 사용되는지에 대해 설명합니다.
토큰의 역할
토큰은 액세스 카드와 비슷한 역할을 합니다. 사용자는 먼저 인가 서버에서 인증을 받고 액세스 토큰을 받습니다. 이 액세스 토큰을 사용해 리소스 서버에 요청을 보낼 때, 리소스 서버는 해당 토큰을 사용해 클라이언트와 사용자에 대한 인증 및 인가 정보를 확인합니다.
토큰의 종류
토큰은 주로 그 안에 데이터를 저장하는 방식에 따라 두 가지로 분류됩니다:
- 비투명(Opaque) 토큰
- 정의: 비투명 토큰은 데이터를 저장하지 않는 토큰입니다. 리소스 서버는 이 토큰 자체로는 아무런 정보를 알 수 없으며, 토큰의 유효성을 검증하기 위해 **내부 검사 호출(Introspection Call)**을 통해 인가 서버에 요청을 보냅니다.
- 용도: 리소스 서버가 인가 서버에 요청을 보내어 토큰에 대한 상세 정보를 얻고, 사용자가 요청한 리소스를 처리하는 데 필요한 인증 및 권한 정보를 확인합니다.
- 비투명하지 않은(Non-opaque) 토큰
- 정의: 비투명하지 않은 토큰은 데이터를 포함하고 있어, 리소스 서버가 별도의 요청 없이도 클라이언트와 사용자에 대한 정보를 확인할 수 있는 토큰입니다. 가장 일반적인 형태는 **JWT (JSON Web Token)**입니다.
- 용도: 리소스 서버는 토큰 자체를 검증하고, 그 안에 포함된 정보(예: 사용자 정보, 권한)를 즉시 사용할 수 있습니다. 별도의 인가 서버에 추가 요청을 할 필요 없이, 토큰만으로 충분한 정보가 제공됩니다.
비투명(Opaque) 토큰 vs. 비투명하지 않은(Non-opaque) 토큰
- 비투명(Opaque) 토큰: 리소스 서버는 이 토큰에 포함된 데이터로는 인증 및 권한 부여 정보를 알 수 없습니다. 대신, 리소스 서버는 인가 서버에 요청을 보내 토큰을 검증하고, 사용자와 클라이언트의 인증 정보를 얻습니다.
- 비투명하지 않은(Non-opaque) 토큰: 이 토큰은 자체적으로 데이터를 포함하고 있어서, 리소스 서버는 별도의 요청 없이 토큰 자체에서 정보를 추출할 수 있습니다. 예를 들어, JWT는 이러한 토큰의 예시로, 서버는 JWT를 사용해 사용자 정보와 클라이언트 인증을 직접 추출하고 처리할 수 있습니다.
결론
OAuth 2에서 토큰은 인증과 인가의 중요한 요소이며, 비투명(Opaque) 토큰과 비투명하지 않은(Non-opaque) 토큰은 각각 다른 방식으로 리소스 서버가 클라이언트 및 사용자에 대한 정보를 처리할 수 있게 합니다. 비투명 토큰은 인가 서버와의 추가 통신을 요구하는 반면, 비투명하지 않은 토큰은 토큰 자체에 모든 필요한 정보를 포함하고 있어, 리소스 서버에서 직접 검증하고 처리할 수 있습니다.
13.2.1 비투명(Opaque) 토큰 사용
비투명(Opaque) 토큰은 리소스 서버가 클라이언트나 사용자의 세부 정보를 직접 확인할 수 있는 정보를 포함하지 않습니다. 이 토큰은 단순히 인증 시도의 증거로서 기능하며, 실제로 사용자가 인증된 상태인지를 판단하는 데 필요한 세부 정보를 제공하지 않습니다. 리소스 서버는 이 토큰을 받은 후, 인가 서버에 추가적인 요청을 보내야만 해당 토큰이 유효한지 확인하고, 사용자나 클라이언트에 대한 세부 정보를 가져올 수 있습니다.
비유: 보물 상자의 열쇠
비투명 토큰은 마치 보물 상자의 열쇠와 같습니다. 상자의 내부를 보려면 열쇠를 사용해 열어봐야 하지만, 열쇠 자체로는 그 안에 무엇이 있는지 알 수 없습니다. 즉, 비투명 토큰은 토큰을 검증하고 그에 대한 정보를 얻는 과정에서만 유효성을 확인할 수 있습니다.
내부 검사 호출
리소스 서버는 비투명 토큰을 처리할 때, **내부 검사 호출(Introspection Call)**을 통해 인가 서버에 요청을 보내야 합니다. 이를 통해 리소스 서버는 비투명 토큰이 유효한지 확인하고, 해당 토큰이 발급된 클라이언트와 사용자에 대한 필요한 세부 정보를 가져옵니다. 이 과정은 다음과 같이 설명할 수 있습니다:
- 리소스 서버는 비투명 토큰을 받습니다.
- 리소스 서버는 인가 서버에 요청을 보내 비투명 토큰의 유효성을 확인합니다.
- 인가 서버는 토큰에 대한 세부 정보를 리소스 서버에 반환합니다.
- 리소스 서버는 이 정보를 기반으로 인증 및 인가 규칙을 적용하고, 요청을 처리합니다.
그림 설명
- 그림 13.5: 비투명 토큰은 보물 상자의 열쇠와 같으며, 열쇠가 작동하는지 확인하려면 시도해봐야 합니다. 유효한 경우, 열쇠로 상자를 열고 그 안에 포함된 정보에 접근할 수 있습니다.
- 그림 13.6: 리소스 서버가 인가 서버에 요청을 보내어 비투명 토큰의 유효성을 확인하고, 해당 토큰이 발급된 클라이언트 및 사용자에 대한 세부 정보를 얻는 과정이 내부 검사 호출로 설명됩니다.
요약
비투명 토큰은 단순히 인증의 증거 역할을 하며, 리소스 서버는 이 토큰을 통해 직접적인 정보를 얻을 수 없습니다. 이를 해결하기 위해 리소스 서버는 인가 서버에 내부 검사 호출을 보내어 토큰의 유효성 및 관련 정보를 확인해야 합니다.
13.2.2 비투명하지 않은(Non-opaque) 토큰 사용
비투명하지 않은(Non-opaque) 토큰은 비투명(Opaque) 토큰과 달리, 토큰 자체에 클라이언트와 사용자에 대한 정보가 포함됩니다. 이러한 토큰은 마치 서명된 문서와 같아서, 리소스 서버가 추가적인 요청 없이도 토큰의 유효성 및 관련 정보를 직접 확인할 수 있습니다.
비유: 서명된 문서
비투명하지 않은 토큰은 서명된 문서에 비유할 수 있습니다. 이 서명된 문서에는 정보를 인증할 수 있는 서명이 포함되어 있기 때문에, 리소스 서버는 토큰이 유효한지 확인하고 토큰에 포함된 데이터(예: 클라이언트 및 사용자 정보)를 바로 사용할 수 있습니다.
JSON Web Token (JWT)
비투명하지 않은 토큰의 가장 일반적인 구현은 **JSON Web Token (JWT)**입니다. JWT는 세 가지 주요 부분으로 구성됩니다:
- 헤더 (Header):
- 토큰에 대한 메타데이터가 포함됩니다. 이 정보에는 토큰을 서명하는 데 사용된 암호화 알고리즘이나, 서명에 사용된 키 식별자 등이 포함됩니다.
- 바디 (Body):
- 실제 인증 정보가 담긴 부분입니다. 클라이언트 및 사용자의 세부 정보(예: 사용자 ID, 권한 수준 등)가 포함됩니다.
- 서명 (Signature):
- 이 부분은 토큰이 인가 서버에서 발급되었음을 증명하며, 토큰이 생성된 후 내용이 변경되지 않았음을 보장하는 암호화된 값입니다. 서명은 헤더와 바디의 데이터를 결합하여 생성됩니다.
이 세 부분은 JSON 형식으로 작성된 후, Base64로 인코딩되어 전달됩니다. 각 부분은 점(.)으로 구분됩니다.
JWT의 구조
- 헤더 + 바디 + 서명의 구조로 구성된 JWT는 예를 들어 다음과 같은 형태를 가집니다:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
비투명 토큰 vs 비투명하지 않은 토큰
- 비투명 토큰은 리소스 서버가 별도의 요청을 보내어 유효성을 검증해야 하므로, 토큰 자체에는 세부 정보가 포함되지 않습니다.
- 반면, **비투명하지 않은 토큰 (예: JWT)**은 세부 정보가 이미 토큰에 포함되어 있어, 리소스 서버는 내부 검사를 하지 않고 바로 필요한 정보를 추출하여 사용할 수 있습니다.
언제 비투명하지 않은 토큰을 사용할까?
- 비투명하지 않은 토큰은 매우 자주 사용되며, 내부 검사를 사용하지 않고 리소스 서버가 즉시 검증할 수 있다는 장점이 있습니다. 그러나, 토큰에 포함된 데이터는 누구나 볼 수 있기 때문에 민감한 정보나 데이터가 많을 경우 보안 문제가 발생할 수 있습니다.
- 데이터가 많거나 민감한 정보가 포함되어 있을 경우, 비투명(Opaque) 토큰이 더 적합할 수 있습니다. 이런 경우 비투명하지 않은 토큰의 사용을 재고하고, 데이터를 토큰 외부에서 처리하거나, 비투명 토큰을 사용하는 것이 바람직합니다.
결론
- **비투명하지 않은 토큰 (JWT)**은 리소스 서버가 즉시 정보를 사용할 수 있게 해주는 장점이 있지만, 보안상 민감한 데이터를 포함하기에 적합하지 않습니다.
- 비투명 토큰은 보안이 중요한 경우나 데이터를 최소화해야 할 때 사용되는 좋은 대안입니다.
13.3 다양한 부여 유형을 통한 토큰 획득
이 섹션에서는 **부여 유형(Grant Type)**에 대해 다룹니다. 부여 유형은 클라이언트가 인가 서버에서 토큰을 획득하는 방식입니다. 앱에서는 클라이언트가 토큰을 얻는 다양한 접근 방식을 제공합니다. 이 섹션에서는 가장 일반적으로 사용되는 세 가지 부여 유형을 다루며, 마지막에는 토큰 만료 후 다시 생성하는 방법에 대해서도 살펴봅니다.
참고 사항
현재 **암시적 부여 유형(Implicit Grant Type)**과 **암호 부여 유형(Password Grant Type)**은 보안상의 이유로 권장되지 않으며, 폐기된 부여 유형입니다. 이러한 부여 유형은 이전 버전에서 사용되었으나, 현대의 보안 요구 사항을 충족하지 못하기 때문에 더 이상 사용하지 않는 것이 좋습니다. 이 책에서는 이 두 부여 유형을 다루지 않으며, 대신 더 안전한 대체 방법을 사용할 것을 권장합니다. 암호 부여 유형에 대해 더 알고 싶다면, 이 책의 첫 번째 판의 12장에서 자세히 다뤄졌습니다. 또한, 암시적 부여 유형의 폐기 이유와 관련된 내용도 다루겠습니다.
13.3.1 인가 코드 부여 유형 (Authorization Code Grant Type)
가장 일반적으로 사용되는 부여 유형은 **인가 코드 부여 유형(Authorization Code Grant Type)**입니다. 이 방식은 사용자가 인증을 제공해야 할 때 사용됩니다. 클라이언트는 먼저 사용자를 인가 서버로 리디렉션하여 인증을 진행하고, 이후 인가 코드를 발급받습니다. 이 코드는 클라이언트가 토큰을 요청할 때 사용됩니다. 이 방식은 주로 웹 애플리케이션에서 사용되며, 보안성이 높고 안전한 인증 방식을 제공합니다.
13.3.2 코드 교환을 위한 증명 키(PKCE)
**PKCE (Proof Key for Code Exchange)**는 인가 코드 부여 유형에 추가적인 보안을 제공하는 요소입니다. PKCE는 클라이언트가 인가 코드를 요청할 때 증명 키를 생성하고, 이 키를 사용하여 코드 교환 시 보안을 강화합니다. 이 방식은 **공개 클라이언트(public client)**에서 사용되며, 리다이렉트 URI를 통해 전달되는 인가 코드가 탈취되는 것을 방지합니다.
13.3.3 인증 없이 토큰을 획득해야 하는 상황
이 절에서는 사용자의 인증 없이 토큰을 획득해야 하는 상황에 대해 다룹니다. 예를 들어, **클라이언트 자격 증명 부여 유형(Client Credentials Grant Type)**이나 리프레시 토큰을 통한 토큰 갱신 등의 상황에서 인증 없이 토큰을 얻을 수 있습니다. 이러한 방법은 주로 서버 간의 통신에서 사용됩니다.
13.3.4 토큰 재발급 방법
토큰이 만료되었을 때, 클라이언트는 **리프레시 토큰(Refresh Token)**을 사용하여 새로운 액세스 토큰을 발급받을 수 있습니다. 이 절에서는 리프레시 토큰을 사용하여 만료된 액세스 토큰을 갱신하는 방법을 설명합니다. 이를 통해 클라이언트는 사용자가 다시 로그인하지 않고도 인증을 유지할 수 있습니다.
13.3.1 인가 코드 부여 유형을 통한 토큰 획득
인가 코드 부여 유형은 오늘날 가장 많이 사용되는 부여 유형으로, 사용자가 인증을 해야 할 때 사용됩니다. 이 부여 유형은 보안을 강화하고, 사용자 인증을 보다 안전하게 처리할 수 있는 방법을 제공합니다. 이 흐름을 이해하기 쉽게 설명하기 위해, 아래에 단계별로 과정을 나열합니다.
- 사용자 행동: 사용자가 앱에서 작업을 하려 합니다. 예를 들어, 메리라는 사용자가 자신의 회사의 청구서를 보려는 경우입니다.
- 클라이언트 앱의 리디렉션: 메리가 사용하는 앱(웹 앱이나 모바일 앱)은 로그인하지 않았기 때문에, 사용자에게 인가 서버에서 제공하는 로그인 페이지로 리디렉션합니다.
- 사용자의 로그인: 메리는 리디렉션된 로그인 페이지에서 자신의 자격 증명을 입력하고, 로그인 버튼을 클릭합니다. 이 페이지는 메리가 액세스하려는 앱이 아니라 중앙 인증 시스템에서 호스팅됩니다.
- 인가 코드 발급: 인가 서버는 메리의 자격 증명이 유효한 경우, 메리가 사용하는 클라이언트 앱(초기 애플리케이션)으로 다시 리디렉션합니다. 이때 인가 서버는 인가 코드라는 고유한 코드를 발급합니다. 이 코드는 클라이언트가 액세스 토큰을 요청하는 데 사용됩니다.
- 액세스 토큰 요청: 클라이언트 앱은 인가 서버에 액세스 토큰을 요청합니다. 클라이언트는 이 액세스 토큰을 사용하여 백엔드 리소스 서버에 요청을 보낼 수 있습니다.
- 액세스 토큰 발급: 인가 서버는 클라이언트가 보낸 인가 코드를 확인하고, 해당 코드가 유효하면 액세스 토큰을 발급하여 응답합니다.
- 백엔드 리소스 서버에 요청: 클라이언트 앱은 발급받은 액세스 토큰을 사용하여 백엔드 리소스 서버에 요청을 보냅니다. 이제 인가된 요청을 통해 필요한 리소스에 접근할 수 있습니다.
흐름에 대한 중요한 관찰
- 리디렉션: 점선 화살표는 리디렉션을 나타내며, 이는 실제 요청과 응답이 아니라 브라우저에서 페이지를 이동하는 과정입니다. 예를 들어, 2단계에서는 사용자가 클라이언트 앱에서 인가 서버의 로그인 페이지로 리디렉션됩니다.
- 인가 코드와 액세스 토큰의 차이: 인가 코드와 액세스 토큰은 다릅니다. 인가 코드는 클라이언트가 액세스 토큰을 얻기 위한 중간 단계에 사용됩니다. 액세스 토큰은 클라이언트가 백엔드 리소스 서버에 승인된 요청을 보낼 때 필요합니다.
- 액세스 토큰을 직접 반환하지 않는 이유: 클라이언트가 액세스 토큰을 직접 받을 수 없도록 하고, 인가 코드 대신 액세스 토큰을 발급하는 방식은 **암시적 부여 유형(Implicit Grant)**이라고 불립니다. 하지만 이 방식은 보안에 취약하여 더 이상 권장되지 않습니다. 리디렉션이 가로채지도록 방지하기 위해, 인가 서버는 인가 코드를 반환하고, 클라이언트가 자격 증명으로 다시 인증해야 하므로 보안이 강화됩니다.
액세스 토큰을 안전하게 얻기 위한 추가 보호
그림 13.10에서는 액세스 토큰을 도난당하지 않도록 인가 코드에 대한 보호를 추가하는 과정을 설명합니다. 클라이언트가 액세스 토큰을 요청할 때 자격 증명으로 인증을 추가하는 방식은, 단순히 인가 코드를 훔친 사람만으로는 액세스 토큰을 얻을 수 없도록 합니다. 악의적인 공격자가 인가 코드를 획득해도, 클라이언트 자격 증명을 알아야 하기 때문에 보안이 강화됩니다.
13.3.2 인가 코드 부여 유형에 PKCE 보호 적용
**PKCE(Proof Key for Code Exchange)**는 인가 코드 부여 유형을 강화하기 위한 보안 메커니즘으로, 주로 클라이언트 자격 증명을 훔쳐 액세스 토큰을 획득하는 것을 방지하는 데 사용됩니다. PKCE는 악의적인 사용자가 클라이언트 자격 증명을 탈취하여 액세스 토큰을 얻는 것을 방지하는 데 중요한 역할을 합니다.
PKCE의 동작 방식
PKCE는 인가 코드 부여 유형의 흐름에서 두 단계에 영향을 미칩니다. 이를 통해, 인가 코드가 가로채지더라도 액세스 토큰을 획득하려면 추가적인 정보를 알아야 하므로 보안이 강화됩니다. 이 흐름은 그림 13.11에서 설명된 두 단계(단계 3과 5)를 기반으로 합니다.
- 검증자(Verifier) 생성: 클라이언트는 무작위 값(바이트 문자열)을 생성합니다. 이 값은 "검증자(verifier)"라고 불리며, 액세스 토큰을 요청할 때 사용할 중요한 값입니다.
- 챌린지(Challenge) 생성: 검증자 값에 대해 해시 함수를 적용하여 "챌린지(challenge)" 값을 생성합니다. 해시 함수는 일방향 함수로, 입력값을 기반으로 고유한 출력을 생성하고, 그 출력을 입력으로 되돌리는 것이 불가능합니다. 이 챌린지는 이후 요청에 사용됩니다.
- verifier = random(); challenge = hash(verifier);
- 클라이언트의 요청 (단계 3): 사용자가 로그인하는 동안, 클라이언트는 이 챌린지를 인가 서버로 보냅니다. 인가 서버는 이 챌린지를 저장하고, 이후 액세스 토큰을 요청할 때 클라이언트가 제공하는 검증자와 비교할 준비를 합니다.
- 클라이언트의 액세스 토큰 요청 (단계 5): 클라이언트가 액세스 토큰을 요청할 때, 검증자 값을 함께 전송합니다. 이 검증자는 단계 3에서 보낸 챌린지와 일치해야 합니다. 만약 클라이언트가 올바른 검증자를 보내면, 인가 서버는 클라이언트가 로그인 요청을 보낸 동일한 앱임을 인식하고 액세스 토큰을 발급합니다.
- 보안 강화:
- 만약 악의적인 사용자가 인가 코드를 훔쳤다고 하더라도, 그들은 검증자 값을 알지 못합니다. 검증자 값은 클라이언트가 요청을 보낼 때만 알 수 있기 때문입니다.
- 또한, 챌린지는 해시 함수로 생성되어, 챌린지를 가로채도 이를 원래의 검증자 값으로 변환할 수 없습니다. 이로 인해 보안이 크게 강화됩니다.
요약
PKCE는 액세스 토큰을 얻기 위한 중요한 보호 장치입니다. 클라이언트 자격 증명을 훔쳐 액세스 토큰을 얻는 공격을 방지하기 위해, 클라이언트는 검증자와 챌린지를 사용하여 인가 서버에서 제공하는 액세스 토큰을 안전하게 요청할 수 있습니다. 이 방식은 인가 코드만으로는 액세스 토큰을 얻을 수 없도록 하여, 보안을 더욱 강화합니다.
13.3.3 클라이언트 자격 증명 부여 유형
클라이언트 자격 증명 부여 유형(Client Credentials Grant Type)은 사용자 개입 없이 앱이 자체 자격 증명을 사용해 액세스 토큰을 얻는 방법입니다. 이 부여 유형은 주로 앱 간 서비스 호출이나 예정된 작업을 처리할 때 사용됩니다. 예를 들어, 타이머가 설정된 이벤트나 자동화된 서비스 간 호출 등이 이에 해당합니다. 이 부여 유형에서는 사용자 인증이 필요하지 않으며, 앱 자체가 인증의 주체가 됩니다.
클라이언트 자격 증명 부여 유형의 흐름
- 액세스 토큰 요청: 앱은 인가 서버에 액세스 토큰을 요청합니다. 이때 앱은 자신의 클라이언트 자격 증명(클라이언트 ID 및 클라이언트 비밀번호)을 사용하여 인증합니다.
- 인가 서버에서 토큰 발급: 클라이언트 자격 증명이 유효한 경우, 인가 서버는 액세스 토큰을 앱에 발급합니다.
- 리소스 서버 요청: 앱은 발급된 액세스 토큰을 사용하여 리소스 서버에 요청을 보내고, 필요한 자원에 접근합니다.
흐름 요약
- 앱은 사용자 인증 없이 자체 자격 증명으로 인증을 받습니다.
- 인가 서버는 유효한 자격 증명에 대해 액세스 토큰을 발급합니다.
- 앱은 이 토큰을 사용하여 리소스 서버에 요청을 보내 필요한 자원을 접근합니다.
그림 13.12에서는 이 프로세스를 시각적으로 보여줍니다. 클라이언트 자격 증명 부여 유형은 사용자가 없는 경우에 유용하며, 앱 간 통신이나 백그라운드 서비스에서 자주 사용됩니다.
13.3.4 리프레시 토큰을 사용하여 새로운 액세스 토큰 얻기
액세스 토큰은 보통 짧은 수명을 가지며, 일반적으로 15분 정도 지속됩니다. 이 짧은 유효 기간은 보안상의 이유로 설정되며, 토큰이 만료되면 리소스 서버는 더 이상 해당 토큰을 허용하지 않습니다. 만약 토큰이 만료되면, 클라이언트는 두 가지 방법 중 하나를 선택할 수 있습니다:
- 부여 유형 단계 반복: 사용자에게 다시 로그인을 요청하여 새 액세스 토큰을 받는 방법입니다.
- 리프레시 토큰 사용: 이전에 발급받은 리프레시 토큰을 사용하여 새 액세스 토큰을 얻는 방법입니다.
리프레시 토큰은 사용자 인증이 필요한 부여 유형(예: 인가 코드 부여 유형)에서 유용하게 사용됩니다. 예를 들어, 사용자가 15분마다 앱에서 다시 로그인해야 하는 불편함을 겪지 않도록, 리프레시 토큰을 사용하여 앱은 자동으로 액세스 토큰을 갱신할 수 있습니다.
리프레시 토큰을 사용하는 단계
- 사용자 데이터 요청: 사용자가 데이터를 요청하려고 시도할 때, 클라이언트는 백엔드에 액세스를 요청합니다.
- 액세스 토큰 만료: 클라이언트의 기존 액세스 토큰이 만료되면, 클라이언트는 리프레시 토큰을 사용해 새 액세스 토큰을 요청합니다.
- 인증 서버에서 새 토큰 발급: 인가 서버는 리프레시 토큰을 확인하고, 클라이언트에게 새 액세스 토큰을 발급합니다.
- 새 액세스 토큰으로 인증: 클라이언트는 새 액세스 토큰을 사용해 백엔드(리소스 서버)에 인증된 요청을 보냅니다.
요약
리프레시 토큰을 사용하면, 액세스 토큰이 만료되었을 때 사용자에게 다시 로그인하라는 메시지를 보내지 않고 자동으로 새 토큰을 얻을 수 있어 사용자 경험을 향상시킬 수 있습니다.
13.4 OpenID Connect가 OAuth 2에 추가하는 것
**OpenID Connect (OIDC)**와 OAuth 2 사이의 차이점에 대한 혼란은 여전히 존재하지만, 사실 OIDC는 OAuth 2 사양을 기반으로 한 프로토콜입니다. 따라서 OAuth 2를 이미 이해하고 있다면, OpenID Connect도 쉽게 이해할 수 있습니다.
OIDC와 OAuth 2의 관계
OAuth 2는 인증 및 권한 부여를 위한 프레임워크이고, OpenID Connect는 OAuth 2 위에 구축된 인증 프로토콜입니다. 쉽게 말하면, OAuth 2는 "허가"를 관리하는 반면, OIDC는 "인증"을 관리합니다.
이 차이를 이해하는 데 도움이 되는 비유는 전기 콘센트를 사용하는 것입니다. 전 세계의 전기 콘센트는 다르지만, 기본적으로 모든 콘센트는 전압을 제공하는 전선이라는 공통적인 기능을 갖추고 있습니다. 그러나 서로 다른 형태의 콘센트는 어댑터가 필요하며, 이 어댑터가 바로 OpenID Connect처럼 OAuth 2에 추가된 변형입니다. OAuth 2를 이해하는 것은 기본적인 전기 콘센트를 이해하는 것과 같고, OpenID Connect는 특정 지역(또는 환경)에서 사용하기 위해 추가적인 변형을 가한 것입니다.
OpenID Connect의 주요 변경 사항
- 스코프(Scope) 값:
- OAuth 2에서는 "scope"가 리소스 서버에 접근할 권한을 정의하는 데 사용됩니다. OIDC에서는 openid, profile 등의 특정 스코프 값이 포함됩니다. 이 값들은 사용자의 인증과 관련된 정보를 요청하는 데 사용됩니다.
- ID 토큰:
- OAuth 2에서는 액세스 토큰을 사용하여 리소스 서버에 접근합니다. OIDC에서는 ID 토큰을 추가로 사용하여 사용자의 인증 정보를 담고 있습니다. 이 ID 토큰은 클라이언트가 사용자를 인증했음을 증명하는데 사용되며, 주로 JWT(JSON Web Token) 형식으로 전달됩니다.
- 아이덴티티 제공자(Identity Provider, IdP):
- OAuth 2에서는 "인가 서버(authorization server)"라는 용어를 사용하지만, OIDC에서는 "아이덴티티 제공자(IdP)"라는 용어를 사용합니다. 이는 OIDC가 사용자 인증에 초점을 맞추기 때문에, 사용자 정보를 제공하는 서버를 이렇게 부릅니다.
요약
OpenID Connect는 OAuth 2의 확장으로, 사용자의 인증을 처리하는 데 필요한 기능을 추가합니다. OAuth 2의 기본적인 인증 및 권한 부여 기능에 ID 토큰과 아이덴티티 제공자를 추가하여, 인증 기능을 강화합니다. OIDC는 OAuth 2를 이해하고 있는 사람에게 추가적인 인증 기능을 제공하여 보다 안전하고 사용자의 정보를 관리할 수 있는 방법을 제시합니다.
13.5 OAuth 2의 오류
이 섹션에서는 OAuth 2 인증 및 권한 부여 시스템을 사용할 때 발생할 수 있는 취약점에 대해 다룹니다. OAuth 2는 매우 유용한 프로토콜이지만, 잘못 구현하거나 보호하지 않으면 보안 위험을 초래할 수 있습니다. 애플리케이션을 개발할 때 이러한 취약점을 이해하고 피하는 것이 중요합니다. 주요 취약점은 다음과 같습니다:
1. Cross-Site Request Forgery (CSRF)
- CSRF는 사용자가 로그인한 상태에서 악성 웹사이트나 애플리케이션이 사용자의 권한을 이용해 원하지 않는 요청을 보내는 공격입니다. OAuth 2에서는 애플리케이션이 CSRF 보호 메커니즘을 제대로 구현하지 않으면 이 공격에 취약해질 수 있습니다. CSRF를 방지하려면 Spring Security와 같은 보안 프레임워크에서 제공하는 CSRF 보호 기능을 활성화해야 합니다.
2. 클라이언트 자격 증명 탈취
- 자격 증명 탈취는 공격자가 클라이언트 애플리케이션의 자격 증명(예: API 키나 비밀번호)을 훔쳐서 이를 사용할 수 있게 되는 공격입니다. 자격 증명이 적절하게 보호되지 않으면 공격자가 이를 탈취해 악용할 수 있습니다. 예를 들어, 자격 증명을 파일에 평문으로 저장하거나 암호화 없이 전송하면 보안 취약점이 발생할 수 있습니다.
3. 토큰 재사용 (Replay Attack)
- OAuth 2에서 액세스 토큰은 리소스 서버에 접근할 때 사용되는 "키"입니다. 하지만 네트워크를 통해 토큰이 전송되는 과정에서 악의적인 사용자가 이를 가로채면, 도난당한 토큰을 재사용하여 리소스에 접근할 수 있습니다. 이를 방지하기 위해 토큰을 안전하게 전송하고, SSL/TLS를 사용하여 암호화하는 것이 중요합니다.
4. 토큰 탈취 (Token Hijacking)
- 토큰 탈취는 공격자가 인증 과정에 개입하여 액세스 토큰을 훔치는 공격입니다. 예를 들어, 리프레시 토큰을 가로채면 공격자는 이를 사용하여 새로운 액세스 토큰을 발급받을 수 있습니다. 이를 방지하려면 리프레시 토큰을 안전하게 저장하고, 액세스 토큰 발급 과정에서 추가적인 보호 조치를 취해야 합니다.
5. OAuth 2는 프레임워크로, 취약점은 잘못된 구현에서 발생
- OAuth 2는 기본적으로 보안을 제공하는 프레임워크입니다. 하지만 잘못된 구현이나 보안 부족한 설정은 심각한 취약점을 초래할 수 있습니다. 예를 들어, Spring Security와 같은 보안 프레임워크를 사용하면 많은 취약점을 완화할 수 있습니다. Spring Security는 OAuth 2의 구현을 잘 처리하고, 보안 취약점을 예방할 수 있는 기능을 제공합니다.
결론
OAuth 2는 강력한 인증 및 권한 부여 시스템이지만, 올바르게 구현하지 않으면 여러 보안 취약점이 발생할 수 있습니다. CSRF, 자격 증명 탈취, 토큰 재사용, 토큰 탈취와 같은 공격을 피하려면, 철저한 보안 설정과 올바른 구현이 필요합니다. 이를 위해 Spring Security와 같은 보안 프레임워크를 활용하고, 전송되는 모든 정보는 암호화하여 보호해야 합니다.
'Spring Security in Action' 카테고리의 다른 글
12장 Implement filtering at the method level (0) | 2024.12.01 |
---|---|
11장 Implement authorization at the method level (1) | 2024.12.01 |
10장 Configuring Cross-Origin Resource Sharing(CORS) (0) | 2024.12.01 |
9장 Configuring Cross-Site Request Forgery(CSRF) protection (0) | 2024.12.01 |
8장 Configuring endpoint-level authorization: Applying restrictions (0) | 2024.12.01 |