JWT 및 NGINX Plus를 사용한 API 클라이언트 인증

NGINX | 2021년 12월 02일

JSON 웹 토큰(JWT, “jots”로 발음)은 신원 정보를 교환하기 위한 간결하고 이식성이 매우 뛰어난 수단입니다. JWT 사양은 OpenID Connect의 중요한 기반으로서 OAuth 2.0 생태계에 싱글 사인온 토큰을 제공합니다. JWT는 그 자체로 인증 자격 증명으로도 사용할 수 있으며, 기존 API 키보다 웹 기반 API에 대한 액세스를 더 효과적으로 제어할 수 있습니다.

NGINX Plus R10 이상에서는 JWT를 직접 검증할 수 있습니다. 이 블로그 게시물에서는 NGINX Plus를 API 게이트웨이로 사용하여 API 엔드포인트에 대한 프런트엔드를 제공하고 JWT로 클라이언트 애플리케이션을 인증하는 방법을 설명합니다.

네이티브 JWT 지원은 NGINX Open Source가 아닌 NGINX Plus에서만 제공됩니다.

편집자 – 이 블로그 게시물은 2021년 12월에 업데이트되어 NGINX Plus R25에서 도입된 auth_jwt_require 지시문을 사용하도록 변경되었습니다. 이 지시문에 대한 자세한 설명은 NGINX Plus R25 발표 블로그의 사용자 지정 JWT 검증 규칙을 참조하세요.

NGINX Plus R15 이상에서는 OpenID Connect 1.0의 ‘인증 코드 흐름’도 제어할 수 있어, 대부분의 주요 ID 공급자와 통합할 수 있습니다. 자세한 내용은 NGINX Plus R15 발표<.htmlspan>를 참조하세요.

JWT의 구조

JWT는 헤더, 페이로드, 서명의 세 부분으로 구성됩니다. 전송될 때는 다음과 같은 형태입니다. 가독성을 위해 줄바꿈을 추가했으며(실제 JWT는 하나의 문자열입니다), 세 부분을 구분할 수 있도록 색상으로 표시했습니다:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewogICAgInN1YiI6ICJsYzEiLAogICAgImVtYWlsIjogImxpYW0uY3JpbGx5QG5naW54LmNvbSIsCn0=.VGYHWPterIaLjRi0LywgN3jnDUQbSsFptUw99g2slfc

그림과 같이 마침표( . )는 헤더, 페이로드, 서명을 구분합니다. 헤더와 페이로드는 Base64로 인코딩된 JSON 객체입니다. 서명은 alg 헤더에 지정된 알고리즘을 사용해 암호화되며, 샘플 JWT를 디코딩하면 이를 확인할 수 있습니다:

인코딩 디코딩헤더 eyJhbGciOiJIUzI1NiIsInR5cCI6Ik pXVCJ9 {
    "alg": "HS256",
    "typ": "JWT"
}
페이로드 ewogICAgInN1YiI6ICJsYzEiLAogICAgImVtYWlsIjogImxpYW0uY3JpbGx5QG5naW54LmNvbSIsCn0= {
    "sub": "lc1",
    "email": "liam.crilly@nginx.com",
}

The JWT 표준은 여러 서명 알고리즘을 정의합니다. 이 예시의 HS256 값은 HMAC SHA‑256을 의미하며, 이 블로그 게시물의 모든 JWT 예시에 사용됩니다. NGINX Plus는 HSxxx, RSxxx, ESxxx 서명 알고리즘을 지원하며, 이러한 알고리즘은 표준에 정의되어 있습니다. JWT는 암호화 방식으로 서명할 수 있으므로 인증 자격 증명으로 사용하기에 적합합니다.

API 키로서의 JWT

API 클라이언트(API 리소스를 요청하는 원격 소프트웨어 클라이언트)를 인증하는 일반적인 방법은 보통 API 키라고 하는 공유 비밀을 사용하는 것입니다. 기존의 API 키는 기본적으로 길고 복잡한 비밀번호로, 클라이언트는 모든 요청에 이를 추가 HTTP 헤더로 전송합니다. 제공된 API 키가 유효한 키 목록에 있으면 API 엔드포인트가 요청된 리소스에 대한 액세스를 허용합니다. 일반적으로 API 엔드포인트가 API 키를 직접 검증하지는 않습니다. 대신 API 게이트웨이가 인증 프로세스를 처리하고 각 요청을 적절한 엔드포인트로 라우팅합니다. 이를 통해 연산 부하를 분산할 수 있을 뿐만 아니라, 여러 API 엔드포인트에 대한 고가용성과 부하 분산 등 리버스 프록시가 제공하는 이점도 얻을 수 있습니다.

기존 API 키를 사용하는 API 클라이언트 및 JWT 인증
API 게이트웨이는 요청을 API 엔드포인트로 전달하기 전에 키 레지스트리를 조회하여 API 키를 검증합니다

API 클라이언트별로 서로 다른 액세스 제어와 정책을 적용하는 것이 일반적입니다. 기존 API 키를 사용할 경우 API 키를 일련의 속성과 연결하기 위한 조회가 필요합니다. 모든 요청마다 이 조회를 수행하면 시스템의 전반적인 지연 시간에 영향을 미칠 수밖에 없습니다. JWT에는 이러한 속성이 포함되어 있으므로 별도로 조회할 필요가 없습니다.

JWT를 API 키로 사용하면 기존 API 키를 대체할 수 있는 고성능 방식을 제공하며, 모범 사례에 따른 인증 기술과 ID 속성 교환을 위한 표준 기반 스키마를 결합할 수 있습니다.

JWT 및 NGINX Plus를 사용한 API 클라이언트와 JWT 인증
NGINX Plus는 요청을 API 엔드포인트로 전달하기 전에 JWT를 검증합니다

NGINX Plus를 인증 API 게이트웨이로 구성하기

JWT를 검증하기 위한 NGINX Plus 구성은 매우 간단합니다.

upstream api_server {
    server 10.0.0.1;
    server 10.0.0.2;
}

server {
    listen 80;

    location /products/ {
        auth_jwt "Products API";
        auth_jwt_key_file conf/api_secret.jwk;
        proxy_pass http://api_server;
    }
}

먼저 upstream 블록에 API 엔드포인트를 호스팅하는 서버의 주소를 지정합니다. location 블록은 /products/로 시작하는 URL에 대한 모든 요청이 인증을 거쳐야 함을 지정합니다. auth_jwt 지시문은 인증에 실패할 경우 401 상태 코드와 함께 반환될 인증 영역을 정의합니다.

The auth_jwt_key_file 지시어는 NGINX Plus가 JWT의 서명 요소를 검증하는 방법을 지정합니다. 이 예에서는 HMAC SHA‑256 알고리즘을 사용하여 JWT에 서명하므로, 서명에 사용되는 대칭 키를 저장할 JSON Web Key를 conf/api_secret.jwk에 생성해야 합니다. 이 파일은 JSON Web Key 사양에 설명된 형식을 따라야 하며, 예시는 다음과 같습니다:

{"keys":
    [{
        "k":"ZmFudGFzdGljand0",
        "kty":"oct",
        "kid":"0001"
    }]
}

대칭 키는 k 필드에 정의되며, 다음은 일반 텍스트 문자열 Base64URL로 인코딩된 값입니다. 해당 문자열은 fantasticjwt입니다. 다음 명령을 실행하여 인코딩된 값을 얻었습니다:

$ echo -n fantasticjwt | base64 | tr '+/' '-_' | tr -d '='

The kty 필드는 키 유형을 대칭 키(옥텟 시퀀스)로 정의합니다. 마지막으로 kid(키 ID) 필드는 이 JSON 웹 키의 일련번호를 정의하며, 여기서는 0001입니다. 이를 통해 auth_jwt_key_file 지시문으로 지정된 동일한 파일에서 여러 키를 지원하고, 해당 키와 그 키로 서명된 JWT의 수명 주기를 관리할 수 있습니다.

이제 API 클라이언트에 JWT를 발급할 준비가 되었습니다.

API 클라이언트에 JWT 발급하기

샘플 API 클라이언트로 '견적 시스템' 애플리케이션을 사용하여 API 클라이언트용 JWT를 생성하겠습니다. 먼저 JWT 헤더를 정의합니다:

{
  "typ":"JWT",
  "alg":"HS256",
  "kid":"0001"
}

The typ 필드는 유형을 JSON Web Token으로 정의하고, alg 필드는 JWT가 HMAC SHA256 알고리즘으로 서명되었음을 나타내며, kid 필드는 JWT가 해당 일련번호를 가진 JSON Web Key로 서명되었음을 나타냅니다.

다음으로 JWT 페이로드를 정의합니다:

{
  "name":"Quotation System",
  "sub":"quotes",
  "iss":"My API Gateway"
}

The sub(주체) 필드는 name 필드의 전체 값을 나타내는 고유 식별자입니다. iss 필드는 JWT 발급자를 나타내며, API 게이트웨이가 타사 발급자 또는 중앙 집중식 ID 관리 시스템에서 발급한 JWT도 허용하는 경우 유용합니다.

이제 JWT를 생성하는 데 필요한 모든 것이 준비되었으므로, 다음 단계에 따라 올바르게 인코딩하고 서명합니다. 명령과 인코딩된 값은 가독성을 위해 여러 줄로 표시되어 있지만, 실제로는 각각 한 줄로 입력되거나 표시됩니다.

  1. 헤더와 페이로드를 각각 한 줄로 만든 후 Base64URL로 인코딩합니다. 
$ echo -n '{"typ":"JWT","alg":"HS256","kid":"0001"}' | base64 | tr '+/' '-_' | tr -d '='eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6IjAwMDEifQ

$ echo -n '{"name":"Quotation System","sub":"quotes","iss":"My API Gateway"}' | base64 | tr '+/' '-_' | tr -d '='
eyJuYW1lIjoiUXVvdGF0aW9uIFN5c3RlbSIsInN1YiI6InF1b3RlcyIsImlzcyI6Ik
15IEFQSSBHYXRld2F5In0
  1. 인코딩된 헤더와 페이로드를 마침표(.)로 연결하고 그 결과를 HEADER_PAYLOAD 변수에 할당합니다.
$ HEADER_PAYLOAD=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6IjAwMDEifQ.eyJuYW1lIjoiUXVvdGF0aW9uIFN5c3RlbSIsInN1YiI6InF1b3RlcyIsIm lzcyI6Ik15IEFQSSBHYXRld2F5In0
  1. 헤더와 페이로드를 대칭 키로 서명하고, 서명을 Base64URL로 인코딩합니다.
$ echo -n $HEADER_PAYLOAD | openssl dgst -binary -sha256 -hmac fantasticjwt | base64 | tr '+/' '-_' | tr -d '='ggVOHYnVFB8GVPE-VOIo3jD71gTkLffAY0hQOGXPL2I
  1. 인코딩된 서명을 헤더와 페이로드에 추가합니다.
$ echo $HEADER_PAYLOAD.ggVOHYnVFB8GVPE-VOIo3jD71gTkLffAY0hQOGXPL2I > quotes.jwt
  1. API 게이트웨이에 인증된 요청을 보내 테스트합니다(이 예시에서는 게이트웨이가 localhost에서 실행 중입니다).
$ curl -H "Authorization: Bearer `cat quotes.jwt`" http://localhost/products/widget1

5단계의 curl 명령은 JWT를 Bearer Token 형식으로 NGINX Plus에 전송하며, 이는 NGINX Plus가 기본적으로 기대하는 형식입니다. NGINX Plus는 쿠키나 쿼리 문자열 매개변수에서도 JWT를 가져올 수 있습니다. 이를 구성하려면 token= 매개변수를 auth_jwt 지시문에 포함하세요. 예를 들어 다음과 같이 구성하면 NGINX Plus에서 이 curl 명령으로 전송된 JWT를 검증할 수 있습니다.

$ curl http://localhost/products/widget1?apijwt=`cat quotes.jwt`
server {
    listen 80;

    location /products/ {
        auth_jwt "Products API" token=$arg_apijwt;
        auth_jwt_key_file conf/api_secret.jwk;
        proxy_pass http://api_server;
    }
}

위와 같이 NGINX Plus를 구성하고 JWT를 생성 및 검증했다면, 이제 API 클라이언트 개발자에게 JWT를 전달하고 각 API 요청에 JWT를 제출하는 데 사용할 방식을 협의할 준비가 된 것입니다.

로깅 및 요청 속도 제한에 JWT 클레임 활용

인증 자격 증명으로서 JWT가 제공하는 주요 이점 중 하나는 JWT 및 해당 페이로드와 관련된 엔터티(예: 발급자, 발급 대상 사용자, 의도된 수신자)를 나타내는 “클레임”을 전달한다는 점입니다. JWT를 검증한 후 NGINX Plus는 헤더와 페이로드에 있는 모든 필드에 변수로 액세스할 수 있습니다. 원하는 필드 앞에 $jwt_header_ 또는 $jwt_claim_를 붙여 이러한 변수에 액세스할 수 있습니다(예: sub 클레임의 경우 $jwt_claim_sub). 따라서 API 자체에 JWT 처리를 구현하지 않고도 JWT에 포함된 정보를 API 엔드포인트로 매우 쉽게 프록시할 수 있습니다.

이 구성 예제에서는 몇 가지 고급 기능을 보여 줍니다.

log_format jwt '$remote_addr - $remote_user [$time_local] "$request" '
               '$status $body_bytes_sent "$http_referer" "$http_user_agent" '
               '$jwt_header_alg $jwt_claim_sub';

limit_req_zone $jwt_claim_sub zone=10rps_per_client:1m rate=10r/s;

server {
    listen 80;

    location /products/ {
        auth_jwt "Products API";
        auth_jwt_key_file conf/api_secret.jwk;

        limit_req zone=10rps_per_client;

        proxy_pass http://api_server;
        proxy_set_header API-Client $jwt_claim_sub;

        access_log /var/log/nginx/access_jwt.log jwt;
    }
}

The log_format 지시어는 jwt라는 새 형식을 정의합니다. 이 형식은 일반 로그 형식에 $jwt_header_alg 및 $jwt_claim_sub 필드를 추가한 것입니다. location 블록 내에서는 access_log 지시어를 사용하여 검증된 JWT에서 얻은 값으로 로그를 기록합니다.

이 예에서는 IP 주소별이 아닌 API 클라이언트별로 API 요청 속도를 제한하기 위해 클레임 기반 변수도 사용합니다. 이는 여러 API 클라이언트가 하나의 포털에 포함되어 있어 IP 주소로 구분할 수 없는 경우에 특히 유용합니다. limit_req_zone 지시문은 JWT의 sub 클레임을 요청 속도 제한 계산의 키로 사용하며, 이렇게 계산된 제한은 location 블록에 limit_req 지시문을 포함하여 적용합니다.

마지막으로 요청이 API 엔드포인트로 프록시될 때 JWT 주체를 새 HTTP 헤더로 제공합니다. proxy_set_header 지시어는 API‑Client라는 HTTP 헤더를 추가하며, API 엔드포인트에서 이를 손쉽게 사용할 수 있습니다. 따라서 API 엔드포인트는 JWT 처리 로직을 구현할 필요가 없습니다. 이는 API 엔드포인트 수가 늘어날수록 더욱 유용합니다.

JWT 폐기

경우에 따라 API 클라이언트의 JWT를 폐기하거나 재발급해야 할 수 있습니다. 간단한 map 블록과 auth_jwt_require 지시어를 함께 사용하면 JWT의 만료일(exp 클레임에 표시됨)에 도달할 때까지 해당 JWT를 유효하지 않은 것으로 표시하여 API 클라이언트의 액세스를 차단할 수 있습니다. 만료일이 지나면 해당 JWT의 map 항목을 안전하게 제거할 수 있습니다.

이 예에서는 토큰의 $jwt_status 변수 값을 0 또는 1로 설정합니다. 이 값은 토큰의 sub 클레임 값($jwt_claim_sub 변수에 캡처됨)에 따라 결정됩니다. 그런 다음 auth_jwt_require 지시어를 location 블록에서 사용하여 토큰을 추가로 검증하거나 거부합니다. 토큰이 유효하려면 $jwt_status 변수가 비어 있지 않고 0(0)이 아니어야 합니다.

map $jwt_claim_sub $jwt_status { "quotes" 0; "test" 0; default 1;
}

server {
    listen 80;

    location /products/ {
        auth_jwt "Products API";
        auth_jwt_key_file conf/api_secret.jwk;
        auth_jwt_require $jwt_status;

        proxy_pass http://api_server;
    }
}

요약

JSON 웹 토큰은 API에 인증된 액세스를 제공하는 데 매우 적합합니다. API 클라이언트 개발자 입장에서는 기존 API 키만큼 간편하게 사용할 수 있으며, 별도의 데이터베이스 조회 없이는 얻을 수 없는 ID 정보를 API 게이트웨이에 제공합니다. NGINX Plus는 JWT 인증을 지원하며 JWT 자체에 포함된 정보를 기반으로 정교한 구성을 구현할 수 있습니다. 또한 다른 API 게이트웨이 기능과 결합하면 NGINX Plus를 통해 API 기반 서비스를 빠르고 안정적이며 확장 가능하고 안전하게 제공할 수 있습니다.

NGINX Plus에서 JWT를 직접 사용해 보려면 지금 30일 무료 평가판을 시작하거나, 사용 사례에 대해 논의하려면 문의해 주세요.


공유
태그: 2021, F5 NGINX, Tech

작성자 소개

관련 블로그 게시물

시청하기: KubeCon North America 2023에서의 NGINX Gateway Fabric
NGINX | 2023. 11. 30.

시청하기: KubeCon North America 2023에서의 NGINX Gateway Fabric

이 영상에서는 KubeCon의 NGINX 부스를 찾아가, 저희가 공개한 Kubernetes Gateway API 구현을 살펴봅니다.

F5 NGINX로 대규모 WAF 플릿을 확인하고 관리하는 2가지 방법
NGINX | 2023. 03. 23.

F5 NGINX로 대규모 WAF 플릿을 확인하고 관리하는 2가지 방법

앱 보안을 강화하는 두 가지 방법을 소개합니다. NGINX Management Suite를 통해 WAF 운영에 대한 가시성을 높이고, NGINX App Protect WAF를 활용해 코드형 보안을 구현하는 것입니다.

Ingress 컨트롤러 선택 가이드, 4부: NGINX Ingress Controller 옵션
NGINX | 2021. 09. 27.

Ingress 컨트롤러 선택 가이드, 4부: NGINX Ingress Controller 옵션

개발 주체, 개발 철학, 프로덕션 환경 준비도, 보안 및 지원을 기준으로 어떤 NGINX Ingress 컨트롤러가 가장 적합한지 알아보세요.

진화하는 공격 환경에 NGINX App Protect Denial of Service가 적응하는 방법
NGINX | 2021. 07. 06.

진화하는 공격 환경에 NGINX App Protect Denial of Service가 적응하는 방법

NGINX App Protect Denial of Service(DoS)가 계층 7 DoS 공격을 방어하기 위해 완화 방법을 실시간으로 조정하는 방식을 설명합니다. 정상적인 사용자 행동과 서버 상태에 대한 모델을 생성하여 공격을 나타내는 이상 징후를 감지하고 맞춤형 완화 시그니처를 적용할 수 있습니다.

NGINX Instance Manager를 활용한 대규모 구성 업데이트 및 인증서 관리
NGINX | 2021. 06. 30.

NGINX Instance Manager를 활용한 대규모 구성 업데이트 및 인증서 관리

NGINX Instance Manager 버전 1.0에는 두 가지 새로운 기능이 도입되었습니다. NGINX 인스턴스에 태그와 사용자 역할을 지정하면 전체 그룹에 구성을 한 번에 적용할 수 있습니다. 인증서 검사는 만료 예정인 인증서를 감지하여 서비스 중단을 방지할 수 있도록 인증서를 교체할 수 있게 해줍니다.

Netflix가 자사 CDN의 핵심으로 NGINX를 선택한 이유
NGINX | 2015. 03. 25.

Netflix가 자사 CDN의 핵심으로 NGINX를 선택한 이유

Netflix가 글로벌 콘텐츠 전송 네트워크인 Open Connect의 스트리밍 미디어 기술로 NGINX를 선택한 이유를 NGINX 개발자 Gleb Smirnoff에게서 알아보세요.