블로그 | NGINX

NGINX 및 NGINX Plus를 사용하여 OAuth 2.0 액세스 토큰 검증

NGINX-F5-수평-검정-유형-RGB의 일부
리엄 크릴리 썸네일
리암 크릴리
2019년 5월 13일 게시

 

이미지는 unsplash.com의 John T.에서 제공되었습니다.

X.509 클라이언트 인증서부터 HTTP 기본 인증까지 API 호출을 인증하는 데에는 다양한 옵션이 있습니다. 그러나 최근 몇 년 동안 OAuth 2.0 액세스 토큰 이라는 사실상의 표준이 등장했습니다. 이는 클라이언트에서 API 서버로 전달되는 인증 자격 증명이며, 일반적으로 HTTP 헤더로 전송됩니다.

하지만 OAuth 2.0은 상호 연결된 표준의 미로입니다. OAuth 2.0 인증 흐름을 발급, 제시 및 검증하는 프로세스는 종종 여러 관련 표준에 의존합니다. 이 글을 쓸 당시에는 OAuth 2.0 표준이 8개 존재했으며, 액세스 토큰이 대표적인 예입니다. OAuth 2.0 핵심 사양( RFC 6749 )에서는 액세스 토큰의 형식을 지정하지 않습니다. 현실 세계에서는 일반적으로 두 가지 형식이 사용됩니다.

  • RFC 7519 에 정의된 JSON 웹 토큰(JWT)
  • 인증된 클라이언트에 대한 고유 식별자에 불과한 불투명 토큰

인증 후 클라이언트는 보호된 리소스에 액세스하기 위해 각 HTTP 요청과 함께 액세스 토큰을 제시합니다. 액세스 토큰의 유효성 검사는 신뢰할 수 있는 ID 공급자(IdP)에서 발급되었으며 만료되지 않았는지 확인하기 위해 필요합니다. IdP가 발급한 JWT에 암호화 방식으로 서명하기 때문에 JWT는 IdP에 대한 런타임 종속성 없이 "오프라인"에서 검증될 수 있습니다. 일반적으로 JWT에는 만료 날짜도 포함되어 있으며 이 날짜도 확인할 수 있습니다. NGINX Plus auth_jwt 모듈은 오프라인 JWT 검증을 수행합니다.

반면, 불투명한 토큰은 해당 토큰을 발급한 IdP로 다시 보내어 검증을 받아야 합니다. 하지만 이 방법은 이전에 로그인한 세션을 활성 상태로 두지 않고도 글로벌 로그아웃 작업의 일부로 IdP가 해당 토큰을 취소할 수 있다는 장점이 있습니다. 글로벌 로그아웃을 사용하려면 IdP에서 JWT의 유효성을 검사해야 할 수도 있습니다.

이 블로그에서는 NGINX와 NGINX Plus가 OAuth 2.0 신뢰 당사자 역할을 하여 검증을 위해 IdP에 액세스 토큰을 전송하고 검증 프로세스를 통과한 요청만 프록시하는 방법에 대해 설명합니다. 이 작업에 NGINX와 NGINX Plus를 사용하는 다양한 이점과 검증 응답을 짧은 시간 동안 캐싱하여 사용자 경험을 어떻게 개선할 수 있는지 논의합니다. NGINX Plus의 경우 NGINX Plus R18 에서 도입된 것처럼 JavaScript 모듈로 키-값 저장소를 업데이트하여 캐시를 NGINX Plus 인스턴스 클러스터에 분산하는 방법도 보여드립니다.

별도로 명시된 경우를 제외하고 이 블로그의 정보는 NGINX 오픈 소스와 NGINX Plus에 모두 적용됩니다. NGINX Plus에 대한 참조는 해당 제품에만 적용됩니다.

토큰 내성

IdP를 사용하여 액세스 토큰을 검증하는 표준 방법을 토큰 내부 검사 라고 합니다. RFC 7662 , OAuth 2.0 토큰 내부 검사는 현재 신뢰 당사자가 IdP에 토큰을 제시하는 데 사용하는 JSON/REST 인터페이스를 설명하고 응답의 구조를 설명하는 널리 지원되는 표준입니다. 이 솔루션은 많은 주요 IdP 공급업체와 클라우드 공급업체에서 지원됩니다.

어떤 토큰 형식을 사용하든 백엔드 서비스나 애플리케이션마다 유효성 검사를 수행하면 많은 중복 코드와 불필요한 처리가 발생합니다. 다양한 오류 조건과 예외 사례를 고려해야 하며, 각 백엔드 서비스에서 이를 수행하면 구현에 일관성이 없고 결과적으로 예측할 수 없는 사용자 경험이 초래됩니다. 각 백엔드 서비스가 다음과 같은 오류 조건을 어떻게 처리하는지 고려해 보세요.

  • 액세스 토큰이 없습니다
  • 매우 큰 액세스 토큰
  • 액세스 토큰에 잘못된 문자 또는 예상치 못한 문자가 있습니다.
  • 여러 액세스 토큰이 제시됨
  • 백엔드 서비스 전반의 시계 오차
토큰 검증을 수행하는 백엔드 애플리케이션

NGINX auth_request 모듈을 사용하여 토큰 검증

코드 중복과 이로 인한 문제를 피하기 위해 NGINX를 사용하여 백엔드 서비스를 대신하여 액세스 토큰을 검증할 수 있습니다. 이것에는 여러 가지 이점이 있습니다:

  • 요청은 클라이언트가 유효한 토큰을 제시한 경우에만 백엔드 서비스에 도달합니다.
  • 기존 백엔드 서비스는 코드 변경 없이 액세스 토큰으로 보호될 수 있습니다.
  • NGINX 인스턴스만(모든 앱이 아님) IdP에 등록하면 됩니다.
  • 동작은 누락 또는 잘못된 토큰을 포함한 모든 오류 조건에서 일관됩니다.
NGINX가 역방향 프록시로 토큰 검증 수행

NGINX가 하나 이상의 애플리케이션에 대한 역방향 프록시 역할을 하면 auth_request 모듈을 사용하여 백엔드로 요청을 프록시하기 전에 IdP에 대한 API 호출을 트리거할 수 있습니다. 잠시 후에 살펴보겠지만 다음 솔루션에는 근본적인 결함이 있지만, 이는 auth_request 모듈의 기본적인 동작을 소개합니다. 이에 대해서는 이후 섹션에서 자세히 설명하겠습니다.

 

auth_request 지시어(5번째 줄)는 API 호출을 처리할 위치를 지정합니다. 백엔드로의 프록싱(6번째 줄)은 auth_request 응답이 성공한 경우에만 발생합니다. auth_request 위치는 9번째 줄에 정의되어 있습니다. 외부 클라이언트가 직접 접근하는 것을 방지하기 위해 내부 로 표시됩니다.

11~14번째 줄은 토큰 내부 검사 요청 형식을 준수하도록 요청의 다양한 속성을 정의합니다. 인트로스펙션 요청에서 전송된 액세스 토큰은 14번째 줄에 정의된 본문의 구성 요소입니다. 여기서 token=$http_apikey는 클라이언트가 apikey 요청 헤더에서 액세스 토큰을 제공해야 함을 나타냅니다. 물론 액세스 토큰은 요청의 모든 속성에서 제공될 수 있으며, 이 경우 다른 NGINX 변수를 사용합니다.

NGINX JavaScript 모듈을 사용하여 auth_request 확장

언급한 대로 이런 방식으로 auth_request 모듈을 사용하는 것은 완전한 해결책이 아닙니다. auth_request 모듈은 HTTP 상태 코드를 사용하여 성공을 판별합니다. 2xx = 좋음, 4xx = 나쁨). 그러나 OAuth 2.0 토큰 내성 응답은 JSON 개체에서 성공 또는 실패를 인코딩하고 HTTP 상태 코드를 반환합니다. 200(좋아요) 두 경우 모두 그렇습니다.

유효한 토큰에 대한 토큰 내성 응답의 JSON 형식

우리에게 필요한 것은 IdP의 인트로스펙션 응답을 적절한 HTTP 상태 코드로 변환해주는 JSON 파서입니다. 이를 통해 auth_request 모듈이 해당 응답을 올바르게 해석할 수 있습니다.

다행히도 JSON 파싱은 NGINX JavaScript 모듈(njs)에서는 간단한 작업입니다. 따라서 토큰 내부 검사 요청을 수행하기 위해 위치 블록을 정의하는 대신, auth_request 모듈에 JavaScript 함수를 호출하라고 지시합니다.

[ 편집자 - 이 게시물은 NGINX JavaScript 모듈의 사용 사례를 살펴보는 여러 게시물 중 하나입니다. 전체 목록은 NGINX JavaScript 모듈의 사용 사례를 참조하세요.

이 섹션의 코드는 다음을 사용하도록 업데이트되었습니다. js_import 지시문은 다음을 대체합니다. js_포함 지시문에 NGINX 플러스 R23 그리고 나중에. 자세한 내용은 NGINX JavaScript 모듈 의 참조 문서를 참조하세요. 예제 구성 섹션에서는 NGINX 구성 및 JavaScript 파일에 대한 올바른 구문을 보여줍니다.

메모: 이 솔루션을 사용하려면 nginx.confload_module 지시문을 사용하여 JavaScript 모듈을 동적 모듈로 로드해야 합니다. 자세한 지침은 NGINX Plus 관리자 가이드를 참조하세요.

 

13번째 줄의 js_content 지시문은 auth_request 핸들러로 JavaScript 함수 introspectAccessToken을 지정합니다. 핸들러 함수는 oauth2.js 에 정의되어 있습니다:

 

introspectAccessToken 함수는 아래 구성 조각에 정의된 다른 위치( /oauth2_send_request )에 대한 HTTP 하위 요청(2번째 줄)을 만드는 것을 주목하세요. 그런 다음 JavaScript 코드는 응답(5번째 줄)을 구문 분석하고 active 필드의 값에 따라 적절한 상태 코드를 auth_request 모듈로 다시 전송합니다. 유효한(활성화된) 토큰이 반환됩니다. HTTP 204 (아니요 콘텐츠) (그러나 성공) 및 잘못된 토큰이 반환됨 HTTP 403 (금지). 오류 조건이 반환됩니다 HTTP 401 (허가되지 않음) 이렇게 하면 오류와 유효하지 않은 토큰을 구별할 수 있습니다.

메모: 이 코드는 개념 증명으로만 제공되며, 프로덕션 품질은 아닙니다. 포괄적인 오류 처리 및 로깅을 갖춘 완벽한 솔루션이 아래에 제공됩니다.

2번째 줄에 정의된 하위 요청 대상 위치는 원래의 auth_request 구성과 매우 유사합니다.

 

토큰 내부 검사 요청을 구성하는 모든 구성은 /_oauth2_send_request 위치에 포함되어 있습니다. 인증(19번째 줄), 액세스 토큰 자체(21번째 줄), 토큰 내부 검사 엔드포인트의 URL(22번째 줄)은 일반적으로 유일하게 필요한 구성 항목입니다. 이 NGINX 인스턴스로부터 토큰 내부 검사 요청을 수락하려면 IdP에 대한 인증이 필요합니다. OAuth 2.0 토큰 내부 검사 사양에서는 인증을 의무화하지만 방법을 지정하지는 않습니다. 이 예에서는 Authorization 헤더에서 Bearer 토큰을 사용합니다.

이러한 구성을 적용하면 NGINX가 요청을 받으면 JavaScript 모듈에 전달하여 IdP에 대한 토큰 내부 검사 요청을 수행합니다. IdP의 응답을 검사하고 active 필드가 true 이면 인증이 성공한 것으로 간주됩니다. 이 솔루션은 NGINX를 사용하여 OAuth 2.0 토큰 내부 검사를 수행하는 간편하고 효율적인 방법이며, 다른 인증 API에 쉽게 적용할 수 있습니다.

하지만 아직 끝나지 않았습니다. 토큰 내부 검사의 가장 큰 과제는 모든 HTTP 요청에 지연 시간이 추가된다는 것입니다. 이는 해당 IdP가 호스팅 솔루션이나 클라우드 공급자인 경우 심각한 문제가 될 수 있습니다. NGINX 및 NGINX Plus는 인트로스펙션 응답을 캐싱하여 이러한 단점에 대한 최적화를 제공할 수 있습니다.

최적화 1: NGINX로 캐싱

OAuth 2.0 토큰 내성은 JSON/REST 엔드포인트에서 IdP에 의해 제공되므로 표준 응답은 HTTP 상태가 포함된 JSON 본문입니다.200 . 이 응답이 액세스 토큰과 비교되는 경우 캐시 가능성이 높아집니다.

유효한 토큰에 대한 완전한 토큰 내성 응답

NGINX는 각 액세스 토큰에 대한 인트로스펙션 응답 사본을 캐시하도록 구성할 수 있으므로 다음에 동일한 액세스 토큰이 제시되면 NGINX가 IdP에 API 호출을 하는 대신 캐시된 인트로스펙션 응답을 제공합니다. 이를 통해 후속 요청에 대한 전반적인 지연 시간이 크게 개선됩니다. 만료되거나 최근에 취소된 액세스 토큰을 수락하는 위험을 완화하기 위해 캐시된 응답을 사용하는 기간을 제어할 수 있습니다. 예를 들어, API 클라이언트가 짧은 시간 안에 여러 번의 API 호출을 한꺼번에 많이 하는 경우 10초의 캐시 유효성이 사용자 경험을 측정 가능하게 개선하는 데 충분할 수 있습니다.

캐싱은 저장소를 지정하여 활성화됩니다. 즉, 캐시(내성 응답)를 위한 디스크의 디렉토리와 키(액세스 토큰)를 위한 공유 메모리 영역입니다.

 

proxy_cache_path 지시어는 인트로스펙션 응답을 위한 /var/cache/nginx/oauth라는 필수 저장소와 키에 대한 token_responses 라는 메모리 영역을 할당합니다. 이는 http 컨텍스트에서 구성되므로 serverlocation 블록 외부에 나타납니다. 캐싱 자체는 토큰 내부 검사 응답이 처리되는 위치 블록 내부에서 활성화됩니다.

 

proxy_cache 지시문(26번째 줄)을 사용하면 이 위치에 대한 캐싱이 활성화됩니다. 기본적으로 NGINX는 URI를 기반으로 캐시하지만 우리의 경우 apikey 요청 헤더(27번째 줄)에 제공된 액세스 토큰을 기반으로 응답을 캐시하려고 합니다.

28번째 줄에서 proxy_cache_lock 지시어를 사용하여 NGINX에 동일한 캐시 키를 가진 동시 요청이 도착하면 다른 요청에 응답하기 전에 첫 번째 요청이 캐시에 채워질 때까지 기다려야 한다는 것을 알려줍니다. proxy_cache_valid 지시어(29번째 줄)는 NGINX에 인트로스펙션 응답을 캐시할 기간을 알려줍니다. 이 지시어가 없으면 NGINX는 IdP에서 보낸 캐시 제어 헤더에서 캐싱 시간을 결정합니다. 그러나 이러한 헤더는 항상 신뢰할 수 있는 것은 아니므로 NGINX에 응답을 캐싱하는 방법에 영향을 줄 수 있는 헤더를 무시하도록 지시합니다(30번째 줄).

이제 캐싱이 활성화되어 액세스 토큰을 제시하는 클라이언트는 10초마다 토큰 내부 검사 요청을 하는 데 따른 지연 비용만 겪게 됩니다.

최적화 2: NGINX Plus를 사용한 분산 캐싱

콘텐츠 캐싱과 토큰 내부 검사를 결합하는 것은 보안에 거의 영향을 미치지 않으면서 전반적인 애플리케이션 성능을 개선하는 매우 효과적인 방법입니다. 그러나 NGINX가 분산 방식으로 배포된 경우(예: 여러 데이터 센터, 클라우드 플랫폼 또는 액티브-액티브 클러스터) 캐시된 토큰 내성 검사 응답은 내성 검사 요청을 수행한 NGINX 인스턴스에서만 사용할 수 있습니다.

NGINX Plus를 사용하면 keyval 모듈 (메모리 내 키-값 저장소)을 사용하여 토큰 내부 검사 응답을 캐시할 수 있습니다. 게다가 zone_sync 모듈을 사용하면 NGINX Plus 인스턴스 클러스터 전체에서 해당 응답을 동기화할 수도 있습니다. 즉, 어떤 NGINX Plus 인스턴스가 토큰 내부 검사 요청을 수행했는지에 관계없이 클러스터 내의 모든 NGINX Plus 인스턴스에서 응답을 사용할 수 있습니다.

메모: 런타임 상태 공유를 위한 zone_sync 모듈 구성은 이 블로그의 범위를 벗어납니다. NGINX Plus 클러스터에서 상태를 공유하는 것에 대한 자세한 내용은 NGINX Plus 관리자 가이드를 참조하세요.

NGINX Plus R18 이상에서는 keyval 지시문에 선언된 변수를 수정하여 키‑값 저장소를 업데이트할 수 있습니다. JavaScript 모듈은 모든 NGINX 변수에 액세스할 수 있으므로 응답 처리 중에 키-값 저장소에 인트로스펙션 응답을 채울 수 있습니다.

NGINX 파일 시스템 캐시와 마찬가지로 키-값 저장소는 저장소를 지정하여 활성화됩니다. 이 경우에는 키(액세스 토큰)와 값(내성 응답)을 저장하는 메모리 영역입니다.

 

keyval_zone 지시문에 timeout 매개변수를 사용하여 auth_request_cache.conf 의 29번째 줄과 같이 캐시된 응답에 대해 동일한 10초 유효 기간을 지정하여 NGINX Plus 클러스터의 각 멤버가 만료 시 독립적으로 응답을 제거합니다. 2번째 줄은 각 항목에 대한 키-값 쌍을 지정합니다. 키는 apikey 요청 헤더에서 제공된 액세스 토큰이고, 값은 $token_data 변수에서 평가한 인트로스펙션 응답입니다.

이제 apikey 요청 헤더가 포함된 각 요청에 대해 $token_data 변수는 이전 토큰 내부 검사 응답(있는 경우)으로 채워집니다. 따라서 토큰 내부 검사 응답이 이미 있는지 확인하기 위해 JavaScript 코드를 업데이트합니다.

 

2번째 줄에서는 이 액세스 토큰에 대한 키‑값 저장소 항목이 이미 있는지 테스트합니다. 인트로스펙션 응답을 얻을 수 있는 경로가 두 가지(키-값 저장소에서 또는 인트로스펙션 응답에서) 있기 때문에 검증 논리를 다음의 별도 함수 tokenResult 로 옮깁니다.

 

이제 각 토큰 내성 응답은 키-값 저장소에 저장되고 NGINX Plus 클러스터의 다른 모든 멤버와 동기화됩니다. 다음 예제는 유효한 액세스 토큰을 사용한 간단한 HTTP 요청과 키-값 저장소의 내용을 표시하기 위한 NGINX Plus API에 대한 쿼리를 보여줍니다.

$ curl -IH "apikey: tQ7AfuEFvI1yI-XNPNhjT38vg_reGkpDFA" http://localhost/ HTTP/1.1 200 OK 날짜: 수, 2019년 4월 24일 17:41:34 GMT 콘텐츠 유형: application/json 콘텐츠 길이: 612 $ 컬 http://localhost/api/4/http/키발/액세스_토큰 {"tQ7AfuEFvI1yI-XNPNhjT38vg_reGkpDFA":"{\"활성\":true}"}

키-값 저장소 자체는 JSON 형식을 사용하므로 토큰 내부 검사 응답에는 따옴표에 이스케이프가 자동으로 적용됩니다.

최적화 3: 내성 응답에서 속성 추출

OAuth 2.0 토큰 내부 검사의 유용한 기능은 응답에 토큰의 활성 상태뿐 아니라 토큰에 대한 정보를 포함할 수 있다는 것입니다. 이러한 정보에는 토큰 만료 날짜와 관련 사용자의 속성(사용자 이름, 이메일 주소 등)이 포함됩니다.

토큰 속성을 사용한 토큰 내성 응답

이런 추가 정보는 매우 유용할 수 있습니다. 기록이 가능하며, 세부적인 액세스 제어 정책을 구현하는 데 사용하거나 백엔드 애플리케이션에 제공할 수 있습니다. 성공적인(HTTP) 추가 응답 헤더로 보내어 이러한 각 속성을 auth_request 모듈로 내보낼 수 있습니다.204 ) 응답.

 

우리는 인트로스펙션 응답(23번째 줄)의 각 속성을 반복하고 이를 응답 헤더로 auth_request 모듈 에 다시 전송합니다. 각 헤더 이름에는 표준 응답 헤더(26번째 줄)와의 충돌을 피하기 위해 Token- 이라는 접두사가 붙습니다. 이제 이러한 응답 헤더를 NGINX 변수로 변환하여 일반 구성의 일부로 사용할 수 있습니다.

 

이 예에서 우리는 사용자 이름 속성을 새로운 변수인 $username 으로 변환합니다(11번째 줄). auth_request_set 지시어를 사용하면 토큰 내부 검사 응답의 컨텍스트를 현재 요청의 컨텍스트로 내보낼 수 있습니다. 각 속성에 대한 응답 헤더(JavaScript 코드에 의해 추가됨)는 $sent_http_token_ 속성 으로 사용할 수 있습니다. 12번째 줄에는 백엔드로 프록시되는 요청 헤더로 $username 에 대한 값이 포함됩니다. 토큰 내부 검사 응답에서 반환된 모든 속성에 대해 이 구성을 반복할 수 있습니다.

토큰 내성 응답에서 프록시된 요청으로 속성 내보내기

생산 구성

위의 코드와 구성 예시는 기능적이며, 개념 증명 테스트나 특정 사용 사례에 대한 사용자 정의에 적합합니다. 실제 운영에 사용하는 경우 추가적인 오류 처리, 로깅 및 유연한 구성을 적극 권장합니다. NGINX 및 NGINX Plus에 대한 보다 강력하고 자세한 구현은 GitHub 저장소에서 찾을 수 있습니다.

요약

이 블로그에서는 NGINX auth_request 모듈을 JavaScript 모듈과 함께 사용하여 클라이언트 요청에서 OAuth 2.0 토큰 내부 검사를 수행하는 방법을 보여드렸습니다. 또한 캐싱 기능을 추가하여 해당 솔루션을 확장하고, NGINX 구성에서 사용하기 위해 인트로스펙션 응답에서 속성을 추출했습니다.

또한 NGINX Plus 키-값 저장소를 NGINX Plus 인스턴스 클러스터 전반의 프로덕션 배포에 적합한 내성 응답을 위한 분산 캐시로 사용하는 방법도 설명했습니다.

NGINX Plus로 OAuth 2.0 토큰 내성을 직접 사용해 보세요. 오늘 무료 30일 평가판을 시작하거나 사용 사례에 대해 논의하려면 저희에게 문의하세요 .


"이 블로그 게시물에는 더 이상 사용할 수 없거나 더 이상 지원되지 않는 제품이 참조될 수 있습니다. 사용 가능한 F5 NGINX 제품과 솔루션에 대한 최신 정보를 보려면 NGINX 제품군을 살펴보세요. NGINX는 이제 F5의 일부가 되었습니다. 이전의 모든 NGINX.com 링크는 F5.com의 유사한 NGINX 콘텐츠로 리디렉션됩니다."