ch13 What are OAuth 2 and OpenID Connect?

2024. 3. 3. 10:32Spring Security

보안과 원활한 인증 방법이 중요한 시대에 OAuth 2 및 OpenID Connect와 같은 프로토콜이 산업 표준으로 등장했습니다. 이 책의 이 부분은 이러한 프로토콜의 복잡성을 해체하여 그들의 메커니즘, 이점 및 잠재적인 위험 요소에 대한 빛을 발합니다. 

13장에서는 두 프로토콜의 개요를 제공하고, 다양한 토큰 그랜트 유형을 설명하며, OAuth 2 내의 잠재적인 취약성을 강조합니다. 

14장에서는 강력한 Spring Security 인가 서버를 설정하는 방법을 자세히 다루며, 클라이언트 세부 정보를 정의하고 암호 키를 관리하는 방법을 강조합니다. 

15장에서는 견고한 리소스 서버를 설정하는 방법을 안내하며, 토큰 검사 및 리소스 보호를 강조합니다. 

16장에서는 인가 서버에서 토큰을 얻고 리소스 서버의 보호 우산 아래에서 리소스에 액세스하는 방법을 보여줍니다. 

이 섹션을 완료하면 애플리케이션에 OAuth 2 및 OpenID Connect를 효과적으로 통합하여 무단 액세스로부터 보호하고 원활한 사용자 경험을 보장할 수 있는 능력을 갖추게 될 것입니다.

 

이 장에서는 다음을 다룹니다:

  • 액세스 토큰의 목적을 이해합니다.
  • OAuth 2 시스템에서 토큰이 발급되고 검증되는 방법을 파악합니다.
  • OAuth 2 / OpenID Connect 시스템에 관여하는 역할을 알아냅니다.

대규모 조직에서 근무하고 매일 다양한 도구를 사용해야 한다고 가정해보십시오. 버그 추적 앱, 업무 문서화 앱, 시간 등록 앱 등을 사용합니다. 이러한 도구들을 사용하려면 각각 인증해야 합니다. 이러한 앱들에 대해 서로 다른 자격 증명을 사용하시겠습니까? 물론, 서로 다른 자격 증명을 사용할 수 있지만, 이 접근 방식은 사용자(여러분)에게 불편할 뿐만 아니라 사용하는 앱의 목적을 복잡하게 만들 수도 있습니다.
여러분에게는 자격 증명을 기억하고 사용하는 각 앱에 여러 번 로그인해야 한다는 점에서 복잡함이 생깁니다. 앱에 추가된 복잡성은 자격 증명을 유지하고 보호하는 능력 및 실제 인증을 구현해야 한다는 사실에서 나타납니다.

 

자격 증명을 저장하고 인증을 관리하는 책임을 별도의 앱으로 관리하는 것은 어떨까요? 이 경우, 사용자는 한 번만 로그인하면 되고 반복해서 인증하는 번거로움 없이 모든 앱을 사용할 수 있습니다. 이러한 솔루션이 있을까요? 네, OAuth 2 사양을 기반으로 이러한 인증을 구현할 수 있습니다. 그리고 더 큰 범위로 진행할 수도 있습니다. 조직 외의 공개 사용자(조직 외의 사용자 - 전 세계를 대상으로 하는 앱)가 사용할 수 있는 앱도 인증 기능이 필요할 수 있습니다. 이러한 기능을 앱에서 구현할 수 있지만:

  • 이 앱에서 인증을 구현하는 데는 더 많은 작업과 노력이 필요합니다.
  • 사용자는 이 앱에 대한 특정 자격 증명이 필요합니다.
  • 사용자는 때로는 사용하는 작은 앱마다 특정 자격 증명을 생성하는 것을 신뢰하지 않을 수 있습니다.

이 앱의 사용자가 이미 가지고 있는 자격 증명으로 로그인할 수 있도록 허용할 수 있을까요? 사용자가 페이스북, GitHub, 트위터, 구글 계정 자격 증명으로 로그인할 수 있을까요? 아마도 이미 많은 곳에서 이를 본 적이 있을 것입니다. 웹 주변의 앱들은 사용자가 가입하고 다양한 소셜 미디어 플랫폼을 사용할 수 있도록 선택할 수 있도록 허용합니다. 이런 방식으로, 앱은 사용자가 이미 가지고 있는 자격 증명으로 인증할 수 있도록 허용하며, 여러분은 앱에 특정 기능을 구현할 필요가 없습니다. 이러한 접근 방식은 다음과 같은 이점이 있습니다:

  • 비용을 줄입니다(인증을 구현하고 유지하는 비용)
  • 사용자 신뢰 문제를 회피합니다(앱이 유지하는 다른 자격 증명을 등록하고 생성해야 하는 문제)
  • 사용자가 사용하는 자격 증명의 수를 최소화하는 데 도움이 됩니다.

OAuth 2는 시스템에서 인증 책임을 분리하는 방법을 알려주는 명세서입니다. 이렇게 함으로써, 여러 앱이 인증을 구현한 다른 앱을 사용할 수 있으며, 사용자가 더 빠르게 인증을 수행하고 그들의 세부 정보를 더 안전하게 유지하며, 앱에서의 구현 비용을 최소화할 수 있습니다.
우리는 13.1절부터 시작하여 OAuth 2 사양을 기반으로 인증 및 인가가 구축된 시스템에 관련된 주요 역할을 소개할 것입니다. 13.1절에서는 사용자, 클라이언트, 인가 서버 및 리소스 서버와 같은 OAuth 2 시스템 구성 요소의 모든 책임을 배울 것입니다. 13.2절에서는 토큰에 대해 논의합니다. 토큰은 앱의 액세스 키와 같습니다. 여러 종류의 토큰을 사용하는 방법과 각 유형을 사용하는 것이 가장 적합한 경우에 대해 배울 것입니다. 13.3절에서는 토큰이 발급될 수 있는 가장 중요한 방법을 검토합니다(이를 14장에서 구현하고 테스트할 것입니다). 마지막으로 13.4절에서는 OAuth 2를 구현할 때 고려해야 할 가능한 위험 요소들을 살펴볼 것입니다.

 

시작하기 전에, 이 장에서는 14장부터 16장까지의 토론을 더 잘 이해하기 위해 필요한 모든 것에 대한 간단한 개요를 제공합니다. 한 장으로 OAuth 2 및 OpenID Connect 전문가로 만들려는 의도는 없습니다. 두 프로토콜은 다른 사람들이 전체 책을 쓸 만큼 복잡하므로, 한 장으로는 불가능하다고 생각합니다. 주제에 대한 지식을 확장하고 싶다면, Justin Richer와 Antonio Sanso의 "OAuth 2 in Action" (Manning, 2017) 및 Prabath Siriwardena의 "OpenID Connect in Action" (Manning, 2023)을 추천합니다.

 

13.1 The big picture of OAuth 2 and OpenID Connect

대기업의 면접에 참석해야 한다고 가정해보겠습니다. 대면 회의를 위해 본사로 호출되었습니다. 그러나 누구나 회사 사무실에 들어갈 수 있는 것은 아닙니다. 방문객을 위한 특정 프로토콜이 구현되어 있습니다.
건물에 들어가서 회의에 참석하려면 먼저 프런트 데스크를 방문하여 신분증을 사용하여 자신을 증명해야 합니다. 신분증이 식별되면 프런트 데스크에서 액세스 카드를 받아서 일부 문들을 열 수 있게 허용됩니다. 모든 엘리베이터를 사용할 수 없을 수도 있고 특정 엘리베이터만 사용할 수도 있습니다 (그림 13.1).

Figure 13.1 The OAuth 2 specification is very similar to accessing an office building.

 

회의를 위해 건물에 들어가는 과정은 OAuth 2 구현에서의 인증 및 인가 작업 방식과 매우 유사합니다. 여러분은 특정 사용 사례를 실행해야 하는 사용자입니다(회의를 위해 특정 방으로 이동). 이를 위해 여러분은 신분증(아이디)을 사용하여 프런트 데스크(인가 서버)에서 인증합니다. 자신이 누구인지 증명한 후에는 액세스 카드(토큰)를 받습니다. 그러나 이 토큰은 특정 리소스(엘리베이터 및 특정 문과 같은)에만 액세스할 수 있습니다. 여러분은 액세스 카드를 짧은 기간 동안만 사용할 수 있습니다. 회의가 끝나면 카드를 프런트 데스크로 반납해야 합니다.
이 섹션에서는 OAuth 2 시스템 내에서 상호 작용하는 책임에 대해 논의하고, 이것이 조직의 본사를 방문하는 것과 어떻게 유사한지 알아보게 될 것입니다. 또한 OAuth 2가 명세서로서 무엇인지와 OpenID Connect(프로토콜)와 OAuth 2(그것이 의존하는 명세서)의 차이에 대해 논의합니다. 이러한 인증 및 인가 접근 방식의 배경 개념을 잘 이해하는 것이 14장부터 16장에서의 구현에 뛰어들기 전에 중요하다고 생각합니다.

 

먼저, OAuth 2 시스템이 어떤 역할을 하는지 알아보겠습니다. 그림 13.2는 OAuth 2 시스템의 주요 역할을 보여줍니다. 여기서 역할이란 시스템 기능에 참여하는 모든 엔터티를 의미합니다. OAuth 2 시스템에서 다음과 같은 역할을 찾을 수 있습니다.

  • 사용자(User): 애플리케이션을 사용하는 사람입니다. 사용자는 일반적으로 클라이언트라고하는 프런트엔드 애플리케이션과 함께 작업합니다. 사용자는 항상 OAuth 2 시스템에 존재하지는 않습니다. 13.3.3절에서 클라이언트 자격 증명 그랜트 유형에 대해 배울 때 이에 대해 논의합니다.
  • 클라이언트(Client): 백엔드를 호출하고 인증 및 인가가 필요한 애플리케이션입니다. 클라이언트는 웹 앱, 모바일 앱, 데스크톱 앱 또는 별도의 백엔드 서비스일 수 있습니다. 클라이언트가 백엔드 서비스인 경우 시스템에는 일반적으로 사용자 역할이 없습니다.
  • 리소스 서버(Resource Server): 하나 이상의 클라이언트 애플리케이션이 보낸 호출을 인증하고 처리하는 백엔드 애플리케이션입니다.
  • 인가 서버(Authorization Server): 인증 및 자격 증명의 안전한 저장을 구현하는 앱입니다.

 

그림 13.2 OAuth 2 시스템의 주요 역할. 사용자는 백엔드 서비스(리소스 서버로 불리는)에서 특정 작업을 위해 인가를 받아야 하는 클라이언트를 사용합니다. 클라이언트의 백엔드에서 인가을 받기 위해 먼저 인가 서버에서 인증되어야 합니다.

 

이제 인증과 권한 부여가 실제로 어떻게 이루어지는지에 대해 이야기해 보겠습니다. 간단히 말하면, 다음과 같은 단계로 이루어집니다.
1. 사용자가 클라이언트 앱을 사용하여 특정 사용 케이스를 실행합니다.
2. 클라이언트 앱은 사용자의 요청을 처리하기 위해 리소스 서버를 호출할 수 있는 권한을 받습니다.
3. 인가를 받기 위해 클라이언트는 먼저 인가 서버에서 토큰(액세스 토큰이라고 함)을 요청합니다. 이 토큰은 클라이언트가, 인가 서버가 클라이언트 자신을 올바르게 식별했다는 것을 증명하는 데 도움이 되는 특정 정보입니다.
4. 클라이언트는 인가 서버가 발급한 토큰을 사용하여 자신의 백엔드(리소스 서버)에 요청을 보낼 때 인가를 받습니다.

 

그림 13.3은 이러한 흐름을 시각적으로 조금 자세히 설명합니다. 그림의 번호가 매겨진 단계는 다음을 나타냅니다.
1. 사용자가 특정 사용 사례를 실행하기 위해 클라이언트 애플리케이션을 사용하려고 합니다.
2. 클라이언트 애플리케이션은 자신의 백엔드를 호출하기 위해 토큰을 먼저 가져야 함을 알고 있습니다. 이를 위해 클라이언트는 인가 서버에서 해당 액세스 토큰을 요청합니다.
3. 클라이언트 앱의 요청에 따라, 인가 서버는 토큰을 발급하고 클라이언트 앱에게 전송합니다.
4. 클라이언트는 앱을 사용하여 요청을 백엔드(리소스 서버)로 보냅니다.
5. 리소스 서버는 클라이언트의 요청을 인가합니다. 성공적으로 인되면 리소스 서버는 클라이언트의 요청을 실행하고 응답합니다.
6. 클라이언트는 결과를 사용자에게 표시합니다.

그림 13.3 OAuth 2 인증 프로세스를 가장 간단하게 설명하는 방법입니다. 클라이언트는 인가 서버에서 토큰을 요청하고 이 토큰을 사용하여 백엔드 앱(리소스 서버)에 요청을 보낼 때 승인을 받습니다.

 

하지만 인가 서버가 발급하는 이 토큰이 정확히 무엇인가요? 토큰은 클라이언트가 인가 서버에 의해 식별되었음을 증명하는 데 사용되는 임의의 데이터 조각(일반적으로 문자열)일 수 있습니다. 토큰은 필요한 경우 사용자와 클라이언트에 대한 자세한 정보를 얻는 방법입니다. 인가 서버가 이제 모든 사용자 및 클라이언트 세부 정보를 관리하므로, 백엔드는 때로는 이러한 세부 정보 중 일부를 인가 서버에서 가져와야 합니다. 백엔드는 이러한 세부 정보를 토큰을 통해 얻게 됩니다. 때로는 토큰 자체에 필요한 세부 정보가 포함되어 있습니다(13.2절에서 읽으실 수 있듯이, 이러한 토큰을 투명 토큰이라고 합니다). 그렇지 않은 경우 백엔드는 클라이언트와 사용자에 대한 데이터를 얻기 위해 인가 서버를 호출해야 합니다(불투명 토큰). 또한, 실제 키와 달리 액세스 토큰은 수명이 길지 않습니다. 액세스 토큰은 짧은 기간(대부분 분 단위) 후에 만료되며, 이후 클라이언트는 다시 다른 토큰을 요청해야 합니다. 이렇게 하면 분실된 토큰(분실된 키와 같은)을 남용할 수 없습니다.
OAuth 2는 클라이언트가 토큰을 얻을 수 있는 여러 가지 흐름을 설명합니다. 이러한 흐름을 "그랜트 타입"이라고 하며, 13.3절에서 가장 일반적으로 사용되는 그랜트 타입을 논의합니다.

 

13.2 Using various token implementations

토큰은 클라이언트가 백엔드(리소스 서버)에 요청을 보낼 때 인가를 받기 위해 사용하는 액세스 카드(그림 13.4)입니다. 토큰은 OAuth 2 인증 및 인가 프로세스의 필수적인 부분이며, 클라이언트와 사용자의 신원을 증명하는 데 사용되지만, 동시에 백엔드가 클라이언트와 사용자에 대한 자세한 정보를 얻는 방법입니다.
이 섹션에서는 토큰이 분류되는 방식 및 토큰 유형에 따라 인가 프로세스에서 어떻게 사용되는지에 대해 논의합니다.

그림 13.4 Zglorb(사용자)는 Mothership(리소스 서버)에 액세스해야 합니다. 이를 위해 먼저 인가 서버에서 인증을 받은 다음 액세스 카드(토큰)를 받습니다. Zglorb는 액세스 카드를 사용하여 Mothership의 특정 영역(리소스)에만 액세스할 수 있습니다.

 

우리는 토큰이 리소스 서버에 인가 데이터를 제공하는 방식에 따라 토큰을 분류합니다.

13.2.1 Using opaque tokens

불투명(Opaque) 토큰은 백엔드가 사용자나 클라이언트를 식별하거나 인가 규칙을 적용하는 데 사용할 수 있는 데이터를 포함하지 않습니다. 불투명 토큰은 단순히 인증 시도의 증거일 뿐입니다. 리소스 서버가 불투명 토큰을 받으면 이 토큰이 유효한지 확인하고 인가 규칙을 적용하기 위해 인가 서버에 추가적인 호출을 해야 합니다.

비투명 토큰은 말 그대로 보물 상자의 열쇠와 같습니다. 어떤 정보도 미리 제공하지 않습니다. 당신은 그것으로 문을 열려고 할 때만 작동한다는 것을 알고 있습니다. 이것이 유효한 것으로 판명되면 보물 상자 안에 있는 것(이 경우에는 사용자 및 클라이언트 세부 정보)에 액세스할 수 있습니다. 그림 13.5는 이 비유를 시각적으로 설명합니다.

 

그림 13.5 비투명 토큰에 대한 비유. 비투명 토큰은 열쇠와 같습니다. 그것이 작동하는지 알기 위해서는 시도해봐야 합니다. 열쇠가 작동하면 열리는 것 안에 무엇이 있는지에도 액세스할 수 있습니다.

 

리소스 서버는 권한 부여 서버에서 제공하는 엔드포인트를 호출하여 불투명 토큰이 유효한지 확인하고 토큰이 발급된 클라이언트 및 사용자에 대해 필요한 세부 정보를 가져옵니다. 이 호출을 내부 검사 호출이라고 합니다(그림 13.6). 리소스 서버에 이러한 세부 정보가 있으면 권한 부여 제약 조건을 적용할 수 있습니다.

 

그림 13.6 내부 검사 호출. 리소스 서버가 인가 서버에 요청을 보내어 비투명 토큰이 유효한지와 해당 토큰이 발급된 대상에 대한 세부 정보를 확인합니다.

 

13.2.2 Using non-opaque tokens

우리가 13.2.1절에서 논의한 비투명 토큰과 달리, 비투명하지 않은 토큰은 인가 서버가 인증 프로세스 중에 토큰을 발급한 클라이언트와 사용자에 대한 정보를 포함합니다. 비투명하지 않은 토큰은 서명된 문서와 비교할 수 있습니다(그림 13.7).

그림 13.7 비투명하지 않은 토큰은 서명된 문서와 같습니다. 이는 리소스 서버가 인가 규칙을 적용하기 위해 필요한 세부 정보와 토큰의 유효성을 확인하기 위한 서명을 포함합니다.

 

비투명하지 않은 토큰의 가장 일반적인 구현은 JSON Web Token (JWT)입니다. JWT는 세 가지 부분으로 구성됩니다 (그림 13.8):

- 헤더(Header): 일반적으로 토큰에 대한 데이터를 포함하며, 토큰을 서명하는데 사용된 암호화 알고리즘이나 인가 서버가 토큰을 서명하는 데 사용한 키 식별자와 같은 정보가 포함됩니다.
- 바디(Body): 일반적으로 토큰이 발급된 개체에 대한 데이터를 포함하며, 클라이언트 및 사용자 세부 정보와 같은 내용이 포함됩니다.
- 서명(Signature): 토큰이 실제로 인가 서버에서 발급되었음을 증명하고, 토큰이 생성된 후에 헤더나 바디의 내용이 변경되지 않았음을 증명하는 데 사용되는 암호화된 값입니다.

 

헤더와 바디에 포함된 데이터는 JavaScript Object Notation (JSON) 형식으로 구성되며, 이후 Base64로 인코딩되어 크기를 줄이고 전송을 더 용이하게 합니다. 세 부분은 점으로 구분됩니다.

그림 13.8 JWT 토큰의 구조. 헤더와 바디에는 토큰의 유효성을 검증하고 인가 규칙을 적용하기 위해 필요한 세부 정보가 포함됩니다.

 

다음 코드 스니펫은 세 부분이 점으로 구분되고 Base64로 인코딩된 JWT의 예시를 보여줍니다.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6I

 

지금 여러분은 아마도 스스로에게 묻고 있을 것입니다. "언제 비투명 토큰을 사용해야하고, 언제 비투명하지 않은 것을 사용해야 할까요?" 이 섹션에서 조금 전에 말했듯이, 비투명하지 않은 토큰은 오늘날 가장 빈번하게 사용되며 내부 검사를 사용하여 유효성을 검증하지 않습니다. 그러나 비투명하지 않은 토큰에는 데이터가 포함되어 있으며, 클라이언트가 이 데이터를 와이어를 통해 백엔드로 전송합니다. 토큰을 획득한 사람은 토큰이 운반하는 데이터도 볼 수 있습니다. 대다수의 경우, 이는 문제가 되지 않습니다. 그리고 나는 누구에게나 토큰 내에 너무 많은 데이터를 보내는 것을 피하도록 권장합니다.
그러나 데이터 양이 많거나 토큰으로 전송하는 것이 안전하지 않은 데이터가 있는 경우 어떻게 해야 할까요? 그런 경우 비투명 토큰이 좋은 대안일 수 있습니다. 먼저 비투명하지 않은 토큰을 고려하고 토큰이 운반할 데이터 양이 너무 많거나 민감한 세부 정보를 전송하고자 할 때만 비투명 토큰으로 되돌아 가도록 권장합니다. 또는 해당 세부 정보를 토큰을 통해 교환하는 것을 피하고자 할 때도 사용할 수 있습니다.

 

13.3 Obtaining tokens through various grant types

이 섹션에서는 부여 유형[grant]에 대해 논의합니다. 부여 유형은 클라이언트가 토큰을 얻는 과정입니다. 앱에서는 클라이언트가 인가 서버에서 토큰을 가져오는 다양한 접근 방식을 찾을 수 있습니다. 우리는 가장 많이 사용되는 세 가지 부여 유형에 대해 논의할 것입니다. 이 섹션의 끝에서는 클라이언트가 토큰이 만료된 후에 어떻게 토큰을 다시 생성할 수 있는지 살펴볼 것입니다.

 

참고: 여전히 암시적 부여 유형과 암호 부여 유형[ implicit grant type and the password grant type ]을 구현한 앱을 발견할 수 있습니다. 이 두 가지 부여 유형은 안전성이 충분하지 않아 폐기되었습니다. 이 책에서는 이 두 가지 부여 유형에 대해 논의하지 않을 것입니다. 앱에서 사용하지 않을 것을 권장합니다. 이 두 가지 부여 유형을 이 섹션에서 논의하는 다른 부여 유형 중 하나로 대체할 수 있습니다. 암호 부여 유형에 대해 더 알고 싶다면, 이 책의 첫 번째 판의 12장에서 좋은 논의를 찾을 수 있습니다. 또한 암시적 부여 유형이 무엇이며 왜 폐기되었는지에 대해 빠르게 살펴볼 것입니다.

 

13.3.1절에서는 사용자의 인증을 허용해야 할 때 가장 많이 사용되는 부여 유형인 인가 코드 부여 유형[ authorization code grant type ]을 다룹니다. 13.3.2절에서는 인가 코드 부여 유형에 대한 추가 요소 인 코드 교환을 위한 증명[proof] 키 (PKCE)를 다룹니다. 13.3.3절에서는 사용자의 인증 없이 토큰을 가져와야 하는 상황을 계속 다루고, 13.3.4절에서는 토큰을 다시 생성하는 방법을 마무리합니다.

 

13.3.1 Getting a token using the authorization code grant type

인가 코드 부여 유형은 오늘날 가장 많이 사용되는 부여 유형입니다. 이 부여 유형은 우리 앱이 사용자를 인증해야 할 때 사용됩니다. 이 부여 유형을 쉽게 이해하기 위해 순서도로 나타낸 그림 13.9를 살펴보세요.


1. 사용자는 사용하는 앱에서 어떤 작업을 하려고 합니다. 예를 들어, 다이어그램의 왼쪽에 있는 소녀가 메리라고 하겠습니다. 메리는 자신이 근무하는 회사가 지급해야 할 모든 청구서[ invoice ]을 보려고 합니다.
2. 메리가 사용하는 앱은 클라이언트입니다. 이 경우 메리는 컴퓨터 앞에 앉아 있으므로 사용하는 클라이언트 앱은 웹 앱입니다. 하지만 메리는 모바일 버전의 앱을 사용할 수도 있습니다. 두 경우 모두 부여 유형은 동일합니다. 메리가 로그인하지 않았기 때문에 앱은 인가 서버가 호스팅하는 로그인 페이지로 리디렉션합니다.
3. 이제 메리는 브라우저에서 로그인 페이지를 볼 수 있습니다. 로그인 페이지는 메리가 액세스한 앱이 아니라 다른 시스템에서 호스팅됩니다. 메리는 리디렉션된 페이지를 회사에서 작업하는 모든 앱에 사용하는 중앙 인증 애플리케이션으로 인식합니다. 메리는 자신의 자격 증명을 사용한 후 브라우저가 청구서 애플리케이션으로 돌아가고 필요한 데이터를 볼 수 있게 될 것이라고 알고 있습니다. 메리는 올바른 자격 증명을 입력하고 로그인 버튼을 선택합니다.
4. 제공된 자격 증명이 올바르기 때문에 인가 서버는 초기 애플리케이션으로 다시 리디렉션합니다. 인가 서버는 또한 초기 애플리케이션 (클라이언트)에 인가 코드라는 고유한 코드를 제공합니다. 클라이언트는 이 코드를 사용하여 액세스 토큰을 가져올 것입니다.
5. 클라이언트는 인가 서버에게 액세스 토큰을 요청합니다. 클라이언트는 이 액세스 토큰이 필요합니다. 이 액세스 토큰은 클라이언트의 백엔드(리소스 서버)에 요청을 보내기 위해 사용됩니다.
6. 인가 코드가 올바르기 때문에 (서버가 단계 5에서 제공한 것과 동일), 인가 서버는 액세스 토큰으로 응답합니다.
7. 클라이언트 앱은 액세스 토큰을 사용하여 백엔드로 요청을 보내고 인가를 받습니다.

Figure 13.9 인가 코드 부여 유형. 사용자가 로그인해야 합니다. 인가 서버는 클라이언트에게 인가 코드를 제공하며, 클라이언트는 이 인가 코드를 사용하여 액세스 토큰을 가져올 수 있습니다. 클라이언트는 액세스 토큰을 사용하여 리소스 서버에게 요청을 인가할 수 있습니다.

 

일련의 관찰 사항을 통해 이 흐름을 더 잘 이해할 수 있습니다:

  • 점선 화살표에 주의하세요. 이들은 브라우저에서의 리디렉션을 나타내는데, 실제로 요청이나 응답을 나타내는 것은 아닙니다. 2단계에서 클라이언트 앱은 사용자를 인가 서버 로그인 페이지로 리디렉션합니다 (브라우저에서 다른 앱의 웹 페이지로 리디렉션됩니다). 4단계에서 인가 서버는 일반적으로 쿼리 파라미로 인가 코드를 제공하면서 클라이언트 앱으로 리디렉션합니다.
  • 메리(사용자)는 4단계에서 7단계까지 알지 못합니다. 로그인 한 후에는 결국 클라이언트에 의해 승인된 후 7단계에 응답으로 인해 송장(invoice)이 표시될 것입니다.
  • 인가 코드와 액세스 토큰을 혼동하지 마십시오. 액세스 토큰은 클라이언트가 최종적으로 백엔드에 승인을 받기 위해 필요한 것입니다(7단계). 그러나 액세스 토큰을 얻기 위해 클라이언트는 먼저 액세스 토큰을 얻기 위해 사용할 인가 코드를 얻습니다(4단계).

 

또한, 권한 및 인증에 대해 처음 접하는 많은 개발자들이 4단계에 대해 혼동합니다. 자주 받는 질문은 "왜 여기서 인가 서버가 액세스 토큰을 직접 반환하지 않는 거죠?"입니다. 클라이언트가 액세스 토큰을 직접 얻을 수 있는 4단계에서 한 번 더 단계를 거쳐야 하는 것이 이상해 보입니다.

그러나 이는 합리적입니다. 실제로 OAuth의 첫 번째 버전에서는 4단계에서 인가 서버가 인가 코드 대신 액세스 토큰을 제공했습니다. 이것이 바로 우리가 지금 "암시적 부여 유형"이라고 부르는 것이며, 더 이상 권장되지 않습니다. 그 이유는 리디렉션은 쉽게 가로챌 수 있으며, 악의적인 개인이 액세스 토큰을 매우 쉽게 얻을 수 있었기 때문입니다. 인가 코드를 반환함으로써, 인가 서버는 클라이언트가 자격 증명으로 다시 인증해야 하는 요청을 다시 보내도록 강제합니다. 따라서 리디렉션을 가로채고 인가 코드를 얻는 경우에도 액세스 토큰을 얻기에는 충분하지 않습니다. 액세스 토큰을 받기 위해 요청을 보내고 토큰을 받으려면 클라이언트 자격 증명도 알아야 합니다.

 

그림 13.10은 누군가가 액세스 토큰을 얻는 것을 방지하기 위해 인가 코드에 보완적인 보호를 추가하는 두 단계를 시각적으로 보여줍니다.

Figure 13.10에서는 로그인 후 인가 코드를 획득하여 클라이언트가 액세스 토큰을 얻기 위해 한 번 더 요청해야 하는 것을 시각적으로 보여줍니다. 이 요청을 보낼 때 클라이언트는 자신의 자격 증명을 사용하여 인증해야 합니다. 이 방법은 액세스 토큰을 도난당하려는 자에게 더 어려움을 줍니다. 왜냐하면 그들은 인가 코드를 훔치는 것뿐만 아니라 클라이언트 자격 증명을 알아야 하기 때문입니다.

 

13.3.2 Applying PKCE protection to the authorization code grant type

나쁜 의도를 가진 개인이 클라이언트 자격 증명도 훔치게 되면 어떻게 될까요? 이 경우, 그들은 액세스 토큰을 획득하고 리소스 서버에 요청을 보낼 수 있습니다. 이런 일이 발생하지 않도록 하는 방법은 없을까요? 네, 코드 교환을 위한 증명 키 (PKCE, 일반적으로 "픽시"로 발음됨)는 보다 안전한 방식으로 인가 코드 플로우를 개선하기 위해 추가된 기능입니다. 이 섹션에서는 PKCE가 클라이언트 자격 증명을 훔쳐 액세스 토큰을 얻는 경우를 방지하는 방법에 대해 논의합니다.
PKCE를 사용하는 것은 인가 코드 그랜트 유형의 두 단계만 영향을 미칩니다. 우리가 13.3.1 절에서 논의한 것과 같습니다. Figure 13.11에서는 단계 3과 5를 나타내는 화살표를 굵게 만들었습니다. 이 두 단계는 PKCE가 적용되는 인가 코드 그랜트 유형의 단계입니다.
1. 먼저, 클라이언트는 무작위 값 생성해야 합니다. 이 값은 무작위 바이트 문자열일 수 있습니다. 이 값을 검증자[ verifier ]라고 합니다.
2. 둘째, 클라이언트는 단계 1에서 생성한 무작위 값에 해시 함수를 적용합니다. 해시 함수는 출력을 입력으로 되돌릴 수 없는 암호화로 특징 지어집니다 (4장). 검증자에 대한 해시 함수의 적용 결과를 챌린지[ challenge ]라고 합니다.

verifier = random();
challenge = hash(verifier);

 

Figure 13.11 클라이언트는 단계 3에서 챌린지를 보내고 단계 5에서 검증자를 보내어 처음으로 사용자에게 로그인하라고 요청한 동일한 클라이언트임을 증명합니다.

 

단계 3에서 클라이언트는 사용자 로그인과 함께 챌린지를 보냅니다. 

인가 서버는 챌린지를 보관하고 단계 5에서 액세스 토큰을 요청할 때 클라이언트가 제공하는 검증자를 기대합니다. 

클라이언트가 단계 5에서 토큰을 요청할 때 보내는 검증자가 단계 3에서 보낸 챌린지와 일치하면, 인가 서버는 토큰을 요청하는 클라이언트 앱이 사용자 인증을 요청한 동일한 앱임을 알 수 있습니다. 

이제 단계 4에서 어떻게든 인가 코드를 얻더라도 액세스 토큰을 얻을 수 없습니다. 왜냐하면 그들은 검증자 값을 알아야 하기 때문입니다. 클라이언트는 아직 이를 전송하지 않았으므로 검증자를 알 수 없습니다. 그리고 단계 3에서 챌린지를 가로채도 검증자를 얻을 수 없습니다. 왜냐하면 챌린지가 해시 함수로 생성되어 출력을 다시 입력으로 변환할 수 없다는 것을 의미합니다.

 

13.3.3 Getting a token with the client credentials grant type

가끔 앱이 사용자 개입 없이 인가를 받아야 할 때가 있습니다. 사용자가 없는 경우 앱은 클라이언트 인가 유형[

client credentials grant type ]을 사용하여 액세스 토큰을 얻어야 합니다. 이 상황은 예정된 프로세스의 타이머와 같은 목적 이벤트가 트리거될 때 서비스가 다른 서비스를 호출해야 할 때 일반적으로 발생합니다. 클라이언트 인가 유형을 사용하면 앱은 자신의 클라이언트 자격 증명으로 인증하기만 하면 됩니다. 다음은 클라이언트 자격 증명 인가 유형의 단계입니다.
1. 앱은 인가 서버에서 액세스 토큰을 요청합니다. 앱은 자신의 자격 증명을 사용하여 인증합니다.
2. 자격 증명이 유효한 경우, 인가 서버가 액세스 토큰을 발급합니다.
3. 앱은 액세스 토큰을 사용하여 리소스 서버에 요청을 보낼 때 인가를 받습니다.

 

Figure 13.12 The client grant type. An app gets an access token without needing a user to authenticate.

 

13.3.4 Using refresh tokens to get new access tokens

토큰에 대해 반드시 기억해야 할 중요한 점은 그들이 비교적 짧은 수명을 가져야 한다는 것입니다. 토큰이 유지되는 정확한 시간은 일반적으로 시나리오에 따라 결정되지만, 보통 15분 정도로 짧으며, 저는 한 시간 이상 지속되는 토큰을 사용한 적이 없습니다. 결국 모든 토큰은 언젠가는 만료됩니다. 토큰이 만료되면 리소스 서버가 더 이상 허용하지 않습니다. 이런 경우에 클라이언트가 토큰을 가지고 있고 토큰이 만료된 경우 두 가지 옵션이 있습니다.
1.  그랜트 유형 단계를 반복하여 새 액세스 토큰을 얻는다. 이는 권한 부여[인가] 코드 부여 유형의 경우 사용자에게 다시 로그인을 요청하는 것을 의미합니다.
2. 새 액세스 토큰을 얻기 위해 리프레시 토큰을 사용합니다.


리프레시 토큰은 사용자가 로그인해야 하는 권한 부여 코드와 같은 부여 유형을 사용하는 경우에 특히 유용합니다. 15분 동안 유효한 토큰이 있다고 상상해 보세요. 사용자로서 매번 앱이 15분마다 다시 로그인하라는 메시지를 받으면 괴로울 것입니다. 대신 앱은 액세스 토큰이 만료될 때마다 사용자에게 다시 로그인을 요청하는 대신 리프레시 토큰을 사용하여 새 액세스 토큰을 얻을 수 있습니다.

 

Figure 13.13는 리프레시 토큰을 사용하는 단계를 보여줍니다:
1. 사용자가 데이터를 얻으려고 시도하면, 이는 클라이언트가 자체 백엔드를 호출해야 함을 의미합니다.
2. 액세스 토큰(이전에 얻은)이 만료되었으므로 클라이언트는 새 액세스 토큰을 받아야 합니다. 클라이언트는 이전에 인증한 사용자임을 증명하기 위해 리프레시 토큰을 보냅니다.
3. 인가 서버는 리프레시 토큰을 인식하고 클라이언트에게 새 액세스 토큰을 제공합니다.
4. 클라이언트는 백엔드(리소스 서버)를 호출하여 새 액세스 토큰으로 인증을 받을 수 있습니다.

 

Figure 13.13에서 보듯이, 클라이언트 앱은 이전에 얻은 액세스 토큰이 만료되면 리프레시 토큰을 사용하여 새 액세스 토큰을 얻을 수 있습니다. 이렇게 함으로써 앱은 사용자에게 다시 인증을 요청하는 것을 피할 수 있습니다.

 

13.4 What does OpenID Connect bring to OAuth 2

OpenID Connect(가끔 OIDC라고 함)와 OAuth 2 사이의 차이점에 대한 혼란이 여전히 많습니다. 이 주제에 대해 과도하게 염려할 필요가 없습니다. "OAuth 2를 이해한다면 OpenID Connect를 사용하는 방법도 알게 될 것"이라고 말하는 것이 일반적입니다.

사실, OIDC는 OAuth 2 사양을 기반으로 구축된 프로토콜입니다. 이러한 이유로 OAuth 2를 이해하면 OIDC를 쉽게 이해할 수 있습니다. 사양과 프로토콜에 대한 비유를 드리겠습니다.

우리는 매일 전기 콘센트를 사용합니다. 전 세계적으로 전기 콘센트의 형태가 다릅니다. 때로는 여행할 때 이것이 실제로 불편할 수 있습니다. 특히 서로 다른 지역 간에 여행할 때 디바이스를 충전할 수 있도록 어댑터를 가져야 할 수도 있습니다.

 

그러나 그 이면에는 모든 콘센트가 동일한 방식으로 작동합니다. 전기 전압을 출력하는 일부 전선이 있습니다. 전 세계의 모든 전기 콘센트가 작동하는 프레임워크를 몇 가지 핵심 요소로 정의할 수 있습니다:

  • 전기 콘센트에는 전기가 흐를 수 있는 세 개의 전선이 있습니다: phase, the null 그리고 grounding 입니다. grounding 는 선택 사항입니다.
  • 전기 콘센트는 약 120볼트 또는 230볼트 정도의 전압을 제공합니다.

걱정하지 마세요. 기술적인 사람이 아니더라도, 스프링 보안을 배우기 위해 이 두 가지 핵심 내용을 이해할 필요는 없습니다. 제 말만 믿으세요.

문제는 전 세계의 모든 소켓이 이러한 명세를 충족하더라도 여전히 여행에 어댑터가 필요한 상황이 발생한다는 것입니다. 그 이유는 그들이 공통 프로토콜을 갖고 있지 않기 때문입니다. 여행을 위해 콘센트를 한 프로토콜에서 다른 프로토콜로 적응시키기 위해 어댑터가 필요합니다(예: 북미에서 유럽으로).

앱과 인증 및 권한 부여의 경우도 마찬가지입니다. 두 앱이 OAuth 2 명세를 준수하더라도, 그들이 완전히 호환되지 않는 상황에서 실행될 수 있고 적응이 필요할 수 있습니다. 이는 두 앱이 동일한 프로토콜을 실행하지 않기 때문입니다. OpenID Connect는 OAuth 2 명세를 제한하여 몇 가지 변경을 도입하는 프로토콜입니다. 주요 변경 사항은 다음과 같습니다.

  • 스코프에 대한 특정 값(예: "프로필" 또는 "openid").
  • 사용자와 클라이언트에 대한 ID 토큰이라는 추가 토큰 사용으로 발급된 토큰에 대한 ID 토큰의 세부 정보 저장.
  • 일반적으로 OIDC를 사용할 때 "그랜트 타입"을 "플로우"로 참조하며 "인가 서버"는 일반적으로 "Identity provider(아이덴티티 제공자)" 또는 "IdP"로 불립니다.

 

13.5 The sins of OAuth 2

이 섹션에서는 OAuth 2 인증 및 권한 부여를 사용하는 애플리케이션의 가능한 취약점에 대해 논의합니다. OAuth 2를 사용할 때 무엇이 잘못될 수 있는지 이해하는 것은 애플리케이션을 개발할 때 이러한 시나리오를 피하기 위해 중요합니다. 물론, 소프트웨어 개발에서 다른 모든 것과 마찬가지로 OAuth 2도 방탄처럼 보이지 않습니다. 우리의 애플리케이션을 개발할 때 인식해야 할 취약점이 있습니다. 여기에는 가장 흔한 몇 가지를 열거해 보겠습니다.

  • 클라이언트에서 Cross-Site Request Forgery (CSRF)를 사용하는 경우 - 사용자가 로그인한 상태에서, 애플리케이션이 CSRF 보호 메커니즘을 적용하지 않으면 CSRF가 발생할 수 있습니다. 우리는 9장에서 Spring Security에서 구현된 CSRF 보호에 대해 좋은 토론을 진행했습니다.
  • 클라이언트 자격 증명 탈취 - 자격 증명을 보호하지 않고 저장하거나 전송하는 것은 공격자가 이를 탈취하고 사용할 수 있는 침해를 초래할 수 있습니다.
  • 토큰 재생 - 13.2절에서 논의한 대로, 토큰은 OAuth 2 인증 및 인가 아키텍처 내에서 리소스에 액세스하는 데 사용되는 "키"입니다. 토큰을 네트워크를 통해 전송하고 때로는 가로채질 수 있습니다. 만약 가로채진다면, 토큰이 도난당하고 재사용될 수 있습니다. 집 앞 문의 열쇠를 잃어버린다면 어떻게 될까요? 다른 사람이 그것을 사용하여 원하는 만큼 문을 열 수 있습니다(재생).
  • 토큰 탈취 - 인증 과정에 개입하여 리소스에 액세스할 수 있는 토큰을 도용하는 것입니다. 이는 새로운 액세스 토큰을 얻기 위해 가로채어 사용할 수 있는 새로 고침 토큰의 잠재적인 취약점입니다. 이 유용한 기사를 참고하십시오. (http://blog.intothesymmetry.com/2015/06/on-oauthtoken-hijacks-for-fun-and.html).

 

OAuth 2는 프레임워크입니다. 취약점은 그것을 잘못 구현한 결과입니다. Spring Security를 사용하면 응용 프로그램에서 대부분의 취약점을 완화할 수 있습니다. Spring Security를 사용하여 응용 프로그램을 구현할 때는 구성을 설정해야 하지만 Spring Security가 구현한 흐름에 의존합니다.
OAuth 2 프레임워크와 관련된 취약점 및 악의적인 사용자가 그것을 어떻게 이용할 수 있는지에 대한 자세한 내용은 Justin Richer와 Antonio Sanso의 OAuth 2 In Action(Manning, 2017)의 3부에서 찾을 수 있습니다: https://livebook.manning.com/book/oauth-2-in-action/part-3.

 

About this Book · OAuth 2 in Action

 

livebook.manning.com

 

13.6 Summary