NGINX Plus API Gateway: JWT 토큰 인증 및 콘텐츠 라우팅 실현

NGINX Plus 릴리스 10부터는 JWT 토큰 인증 을 사용하여 웹 및 API 서비스에서 인증 오프로딩(Offloading)에 대한 지원을 도입했습니다. R10이 출시된 이후로 우리는 각각의 새 릴리스에서 계속해서 기능을 향상시켜 왔습니다.

NGINX Plus R14부터 NGINX Plus API Gateway는 중첩된 클레임(Claim) 및 배열 데이터를 포함하는 JWT를 지원합니다. API Gateway 시나리오에서 사용하는 경우 NGINX Plus는 JWT를 사용하여 Backend 서비스 및 API 대상에 대한 연결을 요청하는 클라이언트를 인증할 수 있습니다.

때때로 NGINX Plus API Gateway를 사용하여 JWT를 인증한 다음 JWT 정보를 기반으로 보다 고급 Load Balancing 결정을 내리는 기본 구성을 제공하라는 요청을 받았습니다. 가장 간단한 해결책은 인증이 성공한 경우 서비스에 대한 액세스를 허용하고, 성공하지 못한 경우 연결을 차단하거나 리다이렉션하는 것입니다.

이 포스트의 설명은 NGINX Plus API Gateway를 사용하는 JWT 인증 및 콘텐츠 기반 라우팅에 대한 완벽한 개념 증명입니다. 가장 광범위한 가능성을 다루고 JWT에 대한 사전 지식 또는 경험의 필요성을 줄이기 위해 JWT에 대한 사전 지식 없이 이 솔루션(예제 및 배경 포함)을 배포할 수 있는 “JWT 101” 실습을 만들었습니다.

환경에 이미 JWT 경험이 있거나 기존 JWT가 있는 경우 처음 두 섹션을 건너뛰고 제공된 NGINX Plus 구성 Snippet을 환경에 맞게 조정하고 JWT 클레임 데이터를 기반으로 고급 Load Balancing 결정을 내릴 수 있습니다.

목차

1. 전제 조건
2. JWT 토큰 인증을 위한 관련 서명 키 생성

3. JWT 토큰 인증을 처리하는 NGINX Plus 구성
3-1. JWT 유효성 검사 및 콘텐츠 기반 라우팅 구성
3-2. JWT 데이터 로깅
4. JWT 토큰 인증 구성 테스트
5. 쿠키(Cookie)에 JWT 전달

1. 전제 조건

이 문서에서는 다음 위치에 기본 구성 파일이 있는 NGINX Plus를 새로 설치하는 것으로 가정합니다.

  • /etc/nginx/nginx.conf
  • /etc/nginx/conf.d/default.conf

모든 CLI 명령은 root 권한을 가정하므로 root가 아닌 사용자는 환경에서 sudo 권한을 가지고 있어야 합니다.

2. JWT 토큰 인증 을 위한 관련 서명 키 생성

아래 지침은 JWT 클레임의 기본 처리를 위해 NGINX Plus를 구성하는 방법을 설명하기 위해 예제와 관련된 페이로드(Payload) 데이터를 사용하여 처음부터 JWT를 생성하는 과정을 안내합니다. 샘플 JWT 대신 기존 JWT를 사용하는 경우 “Secret” 파일에 JWT를 만드는 데 사용하는 서명 키와 일치하는 Base64URL 인코딩 문자열이 포함되어 있는지 확인해야 합니다. JWT 페이로드에서 클레임을 수정해야 할 수도 있습니다.

JWT를 생성하는 방법에 관계없이 Base64URL 인코딩을 사용해야 합니다. 이 인코딩은 패딩(padding)과 일반적으로 Base64 인코딩에 사용되는 기타 Non-HTTP 호환 문자를 올바르게 처리합니다. 많은 도구가 이를 자동으로 처리하지만 수동 CLI 기반 Base64 인코더와 일부 JWT 생성 도구는 그렇지 않습니다. Base64URL 인코딩에 대한 자세한 내용은 Brock Allen의 base64url 인코딩RFC 4648을 참조하세요.

이 예에서는 jwt.io의 GUI를 사용하여 대칭 HS256 JWT를 만듭니다. 다음 이미지는 아래 지침에 지정된 값을 입력하고 서명이 확인된 후 GUI가 어떻게 보이는지 보여줍니다.

JWT 토큰 인증 을 위한 JWT 생성

jwt.io의 GUI에서 작업하면서 오른쪽에 있는 Decoded 열의 필드에 표시된 값을 확인하거나 삽입하여 HS256 JWT를 생성합니다.

1. 다음 기본값이 HEADER 필드에 나타나는지 확인하고 필요한 경우 일치하도록 내용을 수정합니다.

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

2. VERIFY SIGNATURE 필드에서 상자의 값(기본적으로 secret)을 nginx123으로 바꿉니다. (두 단계를 반대로 적용할 경우 발생하는 문제를 방지하기 위해 PAYLOAD 필드에 데이터를 입력하기 전에 이를 변경합니다)

3. PAYLOAD 필드의 내용을 다음으로 바꿉니다.

{
  "exp": 1545091200,
  "name": "Create New User",
  "sub": "cuser",
  "gname": "wheel",
  "guid": "10",
  "fullName": "John Doe",
  "uname": "jdoe",
  "uid": "222",
  "sudo": true,
  "dept": "IT",
  "url": "http://secure.example.com"
}

Note: exp 클레임은 JWT의 만료 날짜 및 시간을 설정하여 UNIX epoch 시간(1970년 1월 1일 자정 UTC 이후의 초 수)으로 나타냅니다.

4. 필드 아래의 막대가 파란색이고 Signature Verified라고 표시되어 있는지 확인합니다.

5. 왼쪽 Encoded 열의 값을 파일이나 버퍼에 복사합니다. http://secure.example.com에 액세스하기 위해 사용자 jdoe가 제공해야 하는 JWT의 전체 텍스트이며, 아래 테스트에서 사용할 것입니다. 여기서는 표시 목적으로 JWT를 줄 바꿈과 함께 표시하지만 NGINX Plus에는 한 줄 문자열로 표시해야 합니다.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NDUwOTEyMDAsIm5hbWUiOi
JDcmVhdGUgTmV3IFVzZXIiLCJzdWIiOiJjdXNlciIsImduYW1lIjoid2hlZWwiLCJndWlkI
joiMTAiLCJmdWxsTmFtZSI6IkpvaG4gRG9lIiwidW5hbWUiOiJqZG9lIiwidWlkIjoiMjIy
Iiwic3VkbyI6dHJ1ZSwiZGVwdCI6IklUIiwidXJsIjoiaHR0cDovL3NlY3VyZS5leGFtcGx
lLmNvbSJ9.YYQCNvzj17F726QvKoIiuRGeUBl_xAKj62Zvc9xkZb4

NGINX Plus 호스트에서 작업하면서 다음 단계에 따라 NGINX Plus가 nginx123으로 서명된 JWT를 확인하는 데 사용하는 키 파일을 만듭니다.

1. 서명 문자열에 해당하는 Base64URL 인코딩 문자열을 생성하려면 이 명령을 실행하십시오. (tr 명령은 Base64URL 인코딩에 필요한 문자 대체를 만듭니다.)

# echo -n nginx123 | base64 | tr '+/' '-_' | tr -d '='
bmdpbngxMjM

/etc/nginx/ 디렉터리에서 JWT 서명을 확인하기 위해 NGINX Plus에서 사용할 api_secret.jwk라는 키 파일을 만들고 다음 내용을 삽입합니다. k 필드의 값은 이전 단계에서 생성한 nginx123의 Base64URL 인코딩 형식입니다.

{"keys":
    [{
        "k":"bmdpbngxMjM",
        "kty":"oct"
    }]
}

3. JWT 토큰 인증 을 처리하는 NGINX Plus API Gateway 구성

이 섹션의 지침은 요청에 포함된 JWT의 유효성을 검사하고 클라이언트가 권한이 있는 경우 보호된 리소스를 제공하도록 NGINX Plus를 구성합니다. 또한 JWT 관련 정보를 캡처하는 새로운 로그 형식을 정의합니다.

3-1. JWT 유효성 검사 및 콘텐츠 기반 라우팅 구성

이 지침에서는 NGINX Plus가 파일을 읽지 않도록 default.conf 구성 파일의 이름을 바꾸고 특별히 테스트용으로 새 구성을 만드는 표준 모범 사례를 따릅니다. 이렇게 하면 테스트를 마쳤거나 테스트 중에 문제가 발생한 경우 기본 구성을 쉽게 복원할 수 있습니다.

1. default.conf 이름 바꾸기

# mv /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf.bak

2. 다음 내용을 포함하여 /etc/nginx/conf.d/jwt-test.conf라는 새 구성 파일을 생성합니다. JWT별 로깅, JWT 유효성 검사 및 콘텐츠 기반 라우팅을 구성합니다(전체 분석은 Snippet을 따릅니다).

server {
    listen       80;
    access_log  /var/log/nginx/host.access.log jwt;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;

        # JWT validation
        auth_jwt "JWT Test Realm" token=$arg_myjwt;
        auth_jwt_key_file /etc/nginx/api_secret.jwk;
        error_log /var/log/nginx/host.jwt.error.log debug;

        if ( $jwt_claim_uid = 222 ) {
            add_header X-jwt-claim-uid "$jwt_claim_uid" always;
            add_header X-jwt-status "Redirect to $jwt_claim_url" always;
            return 301 $jwt_claim_url;
        }

        if ( $jwt_claim_uid != 222 ) {
            add_header X-jwt-claim-uid "$jwt_claim_uid" always;
            add_header X-jwt-status "Invalid user, no redirect" always;
        }
    }
}

location 블록의 지시문은 NGINX Plus에 JWT를 포함하는 HTTP 요청을 처리하는 방법을 알려줍니다. (access_log 지시문으로 정의된 로깅 구성에 대한 자세한 내용은 다음 섹션을 참조하세요.) NGINX Plus는 다음 단계를 수행합니다.

1. 요청 문자열의 myjwt 인수에서 JWT를 추출합니다(auth_jwt 지시문에 대한 토큰 인수로 지정됨).

2. auth_jwt_key-file 지시문(여기서는 api_secret.jwk)에 지정된 서명 키를 사용하여 JWT를 디코딩 합니다. 다음과 같이 페이로드에서 작동합니다(이러한 작업은 JWT 처리에 내재되어 있으며 해당 NGINX Plus 명령어가 없습니다).

  • JWT가 만료되지 않았는지 확인합니다. 즉, 페이로드의 exp 클레임에서 지정한 만료 날짜가 과거가 아니 여야 합니다.
  • 페이로드의 각 클레임에 대해 Key-Value 쌍을 만듭니다. 키 이름은 $jwt_claim_claim-name 형식의 변수입니다(예: uid 클레임의 경우 $jwt_claim_uid).

3. Debug Level에서 모든 오류를 /var/log/nginx/host.jwt.error.log에 기록합니다.

4. $jwt_claim_uid 값이 222(두 개의 if 지시문에 의해 지정됨)인지를 테스트하고 클라이언트에 적절한 응답을 보냅니다. 이것은 JWT의 정보가 콘텐츠 기반 라우팅을 수행하는 데 사용되는 방식입니다.

  • 값이 222이면 NGINX Plus는 클라이언트(return 지시문)를 JWT의 url 클레임에 지정된 URL로 리다이렉션하는 응답(Response)을 보냅니다. 디버깅을 위해 응답에 두 개의 헤더(add_header 지시문)를 추가합니다. 첫 번째 헤더는 uid 클레임의 값을 캡처하고 두 번째 헤더는 클라이언트가 리다이렉션되었다는 사실을 기록합니다.
  • 값이 222가 아닌 경우 NGINX Plus는 기본 인덱스 페이지를 제공합니다(동일한 location 블록의 rootindex 지시문에 의해 정의됨). 다시 디버깅 목적으로 uid 클레임의 값을 캡처하고 클라이언트가 JWT에 지정된 URL에 대한 액세스 권한을 얻지 못했다는 사실을 기록하는 헤더를 추가합니다.

Note: if 지시문을 사용하여 변수를 평가하는 것은 일반적으로 모범 사례로 간주되지 않으며 일반적으로 map 지시문을 대신 사용하는 것이 좋습니다. 그러나 이 간단한 예제를 위해 if 지시문을 사용합니다.

구성은 권한이 부여된 사용자에게만 효과적으로 보호된 리소스에 대한 액세스를 제공합니다. 즉, 유효한 JWT가 있는 사용자는 JWT에 지정된 URL에 액세스할 수 있는 반면 유효한 JWT가 없는 사용자는 기본 페이지에 액세스할 수 있습니다.

3-2. JWT 데이터 로깅

jwt-test.conf의 access_log 지시문에서 참조하는 jwt라는 로깅 형식을 정의하여 콘텐츠 기반 라우팅을 위한 JWT 처리 구성을 완료합니다. 액세스 로그에서 JWT 데이터를 캡처합니다.

1. 다음 log_format 지시문을 /etc/nginx/nginx.conf에 추가합니다.

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

이 형식에는 이 실습에서 사용되는 두 개의 JWT 클레임(uid 및 url)이 포함되지만 $jwt_claim_claim‑name 형식으로 클레임에 해당하는 변수 이름을 사용하여 모든 JWT 클레임 데이터를 기록할 수 있습니다.

2. nginx.conf를 저장한 후 다음 명령을 실행하여 전체 구성(새 jwt-test.conf 파일 포함)의 구문 유효성을 테스트합니다. 보고된 오류를 수정하십시오.

# nginx -t

3. NGINX Plus를 다시 로드합니다.

# nginx -s reload

4. JWT 토큰 인증 구성 테스트

브라우저 또는 curl과 같은 CLI 도구를 사용하여 NGINX Plus가 JWT를 올바르게 검증하고 이를 제공하는 클라이언트를 인증하며 콘텐츠 기반 라우팅을 수행하는지 테스트할 수 있습니다. (인증 및 검증만 테스트하고 콘텐츠 기반 라우팅은 테스트하지 않으려면 jwt-test.conf에서 두 if 블록을 주석 처리하십시오.)

테스트를 실행하기 위해 요청 URL에 myjwt 인수를 포함하여 url 클레임이 222인 JWT의 전체 텍스트를 제공합니다. 다시 표시 목적으로 줄 바꿈을 추가했습니다.

http://example.com/index.html?myjwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiO
jE1NDUwOTEyMDAsIm5hbWUiOiJDcmVhdGUgTmV3IFVzZXIiLCJzdWIiOiJjdXNlciIsImduYW
1lIjoid2hlZWwiLCJndWlkIjoiMTAiLCJmdWxsTmFtZSI6IkpvaG4gRG9lIiwidW5hbWUiOiJqZG9
lIiwidWlkIjoiMjIyIiwic3VkbyI6dHJ1ZSwiZGVwdCI6IklUIiwidXJsIjoiaHR0cDovL3NlY3VyZS5le
GFtcGxlLmNvbSJ9.YYQCNvzj17F726QvKoIiuRGeUBl_xAKj62Zvc9xkZb4

다음은 해당하는 curl 명령입니다.

# curl -v example.com/index.html?myjwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NDUwOTEyMDAsIm5hbWUiOiJDcmVhdGUgTmV3IFVzZXIiLCJzdWIiOiJjdXNlciIsImduYW1lIjoid2hlZWwiLCJndWlkIjoiMTAiLCJmdWxsTmFtZSI6IkpvaG4gRG9lIiwidW5hbWUiOiJqZG9lIiwidWlkIjoiMjIyIiwic3VkbyI6dHJ1ZSwiZGVwdCI6IklUIiwidXJsIjoiaHR0cDovL3NlY3VyZS5leGFtcGxlLmNvbSJ9.YYQCNvzj17F726QvKoIiuRGeUBl_xAKj62Zvc9xkZb4

JWT의 uid 클레임 값이 222이므로 NGINX Plus가 제한된 페이지인 http://secure.example.com의 콘텐츠를 표시할 것으로 예상합니다.

이제 JWT의 url claim이 222가 아닐 때 NGINX Plus가 제한된 페이지의 내용을 표시하지 않고 로컬 서버의 index.claim 페이지(이 경우 http://example.com/index.html)를 표시하는지 확인하기 위해 테스트합니다.

222가 아닌 uid 클레임으로 jwt.io에서 또 다른 JWT를 생성하는 것으로 시작합니다. 예를 들어 111로 만듭니다. 해당 JWT가 포함된 요청 URL은 다음과 같습니다.

우리는 jwt.io에서 222가 아닌 다른 uid 클레임으로 또 다른 JWT를 생성하는 것으로 시작한다. 예를 들어, 우리는 111로 만든다. JWT가 포함된 요청 URL은 다음과 같습니다:

http://example.com/index.html?myjwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiO
jE1NDUwOTEyMDAsIm5hbWUiOiJDcmVhdGUgTmV3IFVzZXIiLCJzdWIiOiJjdXNlciIsImduYW
1lIjoid2hlZWwiLCJndWlkIjoiMTAiLCJmdWxsTmFtZSI6IkpvaG4gRG9lIiwidW5hbWUiOiJqZG9
lIiwidWlkIjoiMTExIiwic3VkbyI6dHJ1ZSwiZGVwdCI6IklUIiwidXJsIjoiaHR0cDovL3NlY3VyZS5l
eGFtcGxlLmNvbSJ9.Ch9xqsGzB8fRVX-3CBuCxP1Ia3oGKB1OnO6qwi_oBgg

curl 명령은 다음과 같습니다.

# curl -v example.com/index.html?myjwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NDUwOTEyMDAsIm5hbWUiOiJDcmVhdGUgTmV3IFVzZXIiLCJzdWIiOiJjdXNlciIsImduYW1lIjoid2hlZWwiLCJndWlkIjoiMTAiLCJmdWxsTmFtZSI6IkpvaG4gRG9lIiwidW5hbWUiOiJqZG9lIiwidWlkIjoiMTExIiwic3VkbyI6dHJ1ZSwiZGVwdCI6IklUIiwidXJsIjoiaHR0cDovL3NlY3VyZS5leGFtcGxlLmNvbSJ9.Ch9xqsGzB8fRVX-3CBuCxP1Ia3oGKB1OnO6qwi_oBgg

이 경우 NGINX Plus는 http://example.com/index.html을 제공할 것으로 예상합니다.

두 테스트 조건 모두에서 헤더 검사 도구(예: curl 또는 일부 브라우저와 함께 제공되는 개발자 도구)를 사용하여 새 헤더인 X-jwt-claim-uidX-jwt-status가 응답에 추가되었는지 확인할 수 있습니다.

테스트 중에 문제가 있으면 /var/log/nginx/host.jwt*에서 Access 및 Error 로그를 확인하십시오. 특히 Error 로그는 확인 문제, 확인 파일 액세스 등을 나타냅니다.

5. 쿠키(Cookie)에 JWT 전달

기본 예제에서 NGINX Plus는 요청 URL의 myjwt 인수에서 JWT를 추출합니다. NGINX Plus는 쿠키(Cookie)에서 JWT 전달도 지원합니다(자세한 내용은 NGINX JWT 참조 문서 참조). jwt-test.conf에서 토큰(Token) 매개변수의 첫 번째 요소가 $arg 대신 $cookie가 되도록 auth_jwt 지시문을 변경합니다.

auth_jwt "JWT Test Realm" token=$cookie_myjwt;

myjwt라는 쿠키에 JWT를 제공하기 위한 적절한 curl 명령은 다음과 같습니다.

# curl -v --cookie myjwt=JWT-text example.com/index.html

NGINX Plus를 직접 사용해 JWT 콘텐츠 기반 라우팅을 테스트해 보려면 지금 30일 무료 평가판을 신청하거나 사용 사례에 대해 최신 소식을 빠르게 전달받고 싶으시면 아래 뉴스레터를 구독하세요.

NGINX STORE 뉴스레터 및 최신 소식 구독하기

* indicates required