NGINX API Gateway API Key/JWT 인증 설정 방법

이 포스트에서는 NGINX API Gateway API Key/JWT 인증을 설정하는 방법에 관해 설명합니다. API Key/JWT와 NGINX의 map 블록을 활용해 API Key 별로 변수를 할당하여 접근 제한을 설정할 수 있고, JWT의 경우 payload의 claim 값에 따라서 접근 제한을 설정할 수 있습니다.
이러한 설정을 통해 API의 보안을 강화하고, 권한이 없는 사용자의 접근을 효과적으로 차단할 수 있습니다.
API Key 인증은 NGINX OSS, NGINX Plus 모두 적용 가능하지만, JWT 인증은 NGINX Plus 전용 기능입니다.

예제에 사용된 NGINX API Gateway 구성은 이전 포스트인 NGINX API Gateway URI rewrite 설정 방법을 참고하세요.
API Gateway 구성 이전에 NGINX 설치가 필요하다면 NGINX 설치(OSS)NGINX Plus 설치를 참고하세요.

목차

1. API Key 인증이란?
2. JWT(JSON Web Token)란?
3. NGINX map 모듈
4. NGINX API Gateway API Key 인증
 4-1. NGINX API Gateway API Key 인증 설정
 4-2. API Key 인증 확인
5. NGINX API Gateway JWT 인증

 5-1. NGINX API Gateway JWT 인증 설정
 5-2. JWT 발행
 5-3. JWT 인증 확인
 5-4. JWT claim 활용 접근 제한 설정
6. 결론

1. API Key 인증이란?

API Key 인증은 클라이언트가 API를 호출할 때 API Key를 사용하여 인증하는 방식입니다. 각 클라이언트는 숫자와 영문으로 구성된 고유한 API Key를 발급받고, API Key를 통해 API에 접근할 수 있습니다.
API Key를 통해 특정 Key를 가진 사용자만 API에 접근할 수 있도록 제한하거나, 인증되지 않은 사용자의 접근을 차단할 수 있습니다.

2. JWT(JSON Web Token)란?

JWT는 header, payload, signature로 구성된 클라이언트를 인증하고 식별하기 위한 토큰입니다.
Header에는 주로 암호화에 사용할 알고리즘, 토큰의 타입이 들어있으며, payload에는 클라이언트의 정보인 claim이 들어있습니다. Signature는 header와 payload의 값을 지정된 알고리즘과 비밀 키를 통해 암호화한 값입니다.
인코딩된 JWT는 ‘<header>.<payload>.<signature>’와 같이 점으로 구분됩니다.
클라이언트는 JWT를 이용하여 서버에 인증 정보를 전달하며, 서버는 서명을 확인하여 해당 토큰의 유효성을 검증합니다.

주의: JWT의 header와 payload는 단순히 Base64 형식으로 인코딩된 값으로, 노출되면 안 되는 중요한 정보를 담지 않도록 주의가 필요합니다.

3. NGINX map 모듈

NGINX의 map 모듈은 요청의 특정 변수를 기반으로 다른 변수를 설정할 수 있습니다. 이를 통해 요청의 특정 조건에 따라 값을 동적으로 할당하고, 조건부 설정을 간단하게 처리할 수 있습니다.

map 블록은 http 블록 내부에서 다음과 같은 형식으로 구성할 수 있습니다.

map $remote_addr $client_location {
        192.168.1.1   "local";
        203.0.113.1   "external";
        default       "unknown";
    }

위와 같은 예의 경우, 요청의 $remote_addr(클라이언트 IP)변수에 따라 $client_location 변수를 할당합니다. 명시된 IP(192.168.1.1, 203.0.113.1)의 요청을 제외한 모든 요청은 default 설정을 따릅니다.

NGINX의 map 모듈에 대한 보다 자세한 내용은 ngx_http_map_module을 참고하세요.

4. NGINX API Gateway API Key 인증

예제의 API Key 인증 구성을 위한 NGINX 디렉토리 구조는 다음과 같습니다.

/etc/nginx/
├── api_gateway.conf        # NGINX API Gateway 설정 파일
├── auth
│   └── api_keys.conf      # API Key 관리 파일
├── conf.d
│   ├── path1.conf         # API Gateway가 프록시할 백엔드 서버 파일
│   └── path2.conf
└── nginx.conf              # 최상위 NGINX 설정 파일

4-1. NGINX API Gateway API Key 인증 설정

1. API Key 관리를 위한 api_keys.conf 파일을 작성합니다.

요청의 헤더를 통해 제공받은 API Key 값에 따라 접근 권한을 설정하기 위해 map 블록을 통해 새 변수를 할당했습니다.

# /etc/nginx/auth/api_keys.conf

map $http_apikey $api_client_name {
        default "";

        "xg72Fi/D0ZB6OAGtSg24Ck0h" "admin";
        "mMsJz9qPH68iUiX7ZKz9K6Hg" "user";
}

$http_<헤더 이름>과 같이 설정하여, Apikey를 검증하기 위한 헤더 이름을 지정할 수 있습니다. $http_apikey 변수를 통해 요청의 Apikey 헤더 값에 따라 $api_client_name 변수를 할당합니다.
헤더를 통해 API Key를 전달받지 못했을 때 $api_client_name 변수는 공란으로 설정됩니다.

API Key 값으로 사용된 문자열은 다음 명령어를 통해 무작위로 생성했습니다.

$ openssl rand -base64 18

2. 작성한 api_keys.conf 파일을 api_gateway.conf 파일에 include 합니다.

# /etc/nginx/api_gateway.conf

include /etc/nginx/auth/api_keys.conf;

3. api_gateway.conf 파일에 API Key 검증 및 접근 제한을 위한 location 블록을 구성합니다.

# /etc/nginx/api_gateway.conf

include /etc/nginx/auth/api_keys.conf;

server {
    listen       80;
    server_name  localhost;

    access_log  /var/log/nginx/default_access.log main;
    error_log   /var/log/nginx/default_error.log;

    location /api/path1 {
            proxy_pass http://192.168.200.174:81;
    }

    location /api/path2 { 
            proxy_pass http://192.168.200.174:82;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }


location = /key_auth{
            internal;                          # 검증하기 위한 경로로 외부 접근 차단

            if ($http_apikey = "") {           # 헤더를 통해 API Key가 제공되지 않을 경우
                    return 401;                # Unauthorized 반환
            }
            if ($api_client_name != "admin") { # API Key에 할당된 변수가 admin이 아닐 경우
                    return 403;                # Forbidden 반환
            }

            return 204 ;                       # 인증을 위한 경로로 204 반환 후 proxy_pass 진행

    }

if 문 설정에 따라 API Key가 헤더를 통해 제공되지 않을 경우 401 응답 코드를 반환하고, API Key가 제공되어도, 해당 Key에 할당된 $api_client_name 변수가 admin이 아닐 경우 403 응답 코드를 반환합니다.

4. API Key 검증 적용을 위한 블록에 auth_request 지시문을 작성합니다. auth_request 지시문은 http, server, location 블록 모두 적용할 수 있습니다. 예제에서는 location 블록(/api/path2 경로)에 적용했습니다.

# /etc/nginx/api_gateway.conf

location /api/path2 {
            auth_request /key_auth;
            proxy_pass http://192.168.200.174:82;
    }

전체 구성을 확인하면 다음과 같습니다.

# /etc/nginx/api_gateway.conf

include /etc/nginx/auth/api_keys.conf;

server {
    listen       80;
    server_name  localhost;

    access_log  /var/log/nginx/default_access.log main;
    error_log   /var/log/nginx/default_error.log;

    location /api/path1 {
            proxy_pass http://192.168.200.174:81;
    }

    location /api/path2 { 
            auth_request /key_auth;
            proxy_pass http://192.168.200.174:82;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

    location = /key_auth{
            internal;

            if ($http_apikey = "") {
                    return 401;
            }
            if ($api_client_name != "admin") {
                    return 403;
            }

            return 204 ;

    }
}

5. 변경된 설정을 적용합니다.

$ sudo nginx -s reload

4-2. API Key 인증 확인

1. API Key를 포함하지 않은 요청을 전송합니다.

$ curl http://192.168.200.174/api/path2             

<html>
<head><title>401 Authorization Required</title></head>
<body>
<center><h1>401 Authorization Required</h1></center>
<hr><center>nginx/1.25.4</center>
</body>
</html>

401 응답 코드를 반환합니다.

2. user 값이 할당된 API Key를 포함한 요청을 전송합니다.

$ curl -H "apikey: mMsJz9qPH68iUiX7ZKz9K6Hg" http://192.168.200.174/api/path2

<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.25.4</center>
</body>
</html>

403 응답 코드를 반환합니다.

3. admin 값이 할당된 API Key를 포함한 요청을 전송합니다.

$ curl -H "apikey: xg72Fi/D0ZB6OAGtSg24Ck0h" http://192.168.200.174/api/path2

Server address: 192.168.200.174:82
Server name: Path2
URI: /api/path2
Request ID: bf0b6bb065a1caac5d047542f70c6ad2

정상적으로 응답을 반환하는 것을 확인할 수 있습니다.

5. NGINX API Gateway JWT 인증

JWT를 통한 인증 방식은 NGINX Plus 전용 기능입니다.

예제의 JWT 인증 구성을 위한 NGINX 디렉토리 구조는 다음과 같습니다.

/etc/nginx
├── api_gateway.conf       # API Gateway 설정 파일
├── auth
│   ├── api_secret.jwk     # JWT 인증을 위한 Key 설정 파일
│   └── jwt_claim.conf     # JWT calim 값을 통한 접근 제한 설정 파일
├── conf.d
│   ├── path1.conf         # API Gateway가 프록시할 백엔드 서버 파일
│   └── path2.conf
└── nginx.conf             # 최상위 NGINX 설정 파일

5-1. NGINX API Gateway JWT 인증 설정

1. api_gateway.conf 파일에 JWT 인증 적용을 위한 지시문을 작성합니다. 예제에서는 /api/path2 경로에 JWT 인증을 적용했습니다.

server {
    listen       80;
    server_name  localhost;

    access_log  /var/log/nginx/default_access.log main;
    error_log   /var/log/nginx/default_error.log;

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

    location /api/path1 {
            proxy_pass http://192.168.200.174:81;
    }

    location /api/path2 {
            auth_jwt "path2 API" ;                    # JWT 검증을 위한 지시문
            auth_jwt_key_file auth/api_secret.jwk;    # JWT 키 파일의 경로 지정
            proxy_pass http://192.168.200.174:82;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

}

auth_jwt 지시문을 통해 JWT 인증을 활성화합니다. 해당 지시문의 문자열(path2 API)은 realm으로서 기능합니다. Realm은 인증 요청 시 사용자에게 어떤 리소스나 서비스에 접근하려는지 알려주는 역할을 합니다.

auth_jwt_key_file 지시문은 JWT 인증에 사용할 jwk(Jason Web Key) 파일의 경로를 지정합니다.

2. auth_jwt_key_file 지시문에서 지정한 경로에 JWT 키 파일을 생성합니다. 해당 키 파일은 JWT key 파일 형식을 따라야 합니다.

# /etc/nginx/auth/api_secret.jwk

{"keys":    [{
        "k": "bmdpbnhzdG9yZWtleQ",  # secret key값
        "kty": "oct",               # key 타입 정의
        "kid": "0001"               # key의 시리얼 넘버
    }]
}

k의 값은 secret key 값으로 nginxstorekey 문자열을 Base64 형식으로 인코딩한 값입니다. 다음 명령어를 통해 생성했습니다.

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

bmdpbnhzdG9yZWtleQ

5-2. JWT 발행

JWT 발행은 jwt.io에서 진행했습니다.

1. header 값을 입력합니다.

NGINX API Gateway API Key/JWT - JWT header

JWT 서명 알고리즘과, api_secret.jwk 파일에서 작성한 키의 시리얼 넘버를 입력합니다.

2. payload 값을 입력합니다.

NGINX API Gateway API Key/JWT - JWT payload

예제를 위해 name, iss claim을 사용했습니다.

주의: Header, payload 값은 단순히 Base64 형식으로 인코딩되므로, 노출되면 안 되는 중요한 정보를 담지 않도록 합니다.

3. verify signature 항목을 작성합니다.

NGINX API Gateway API Key/JWT - JWT signature

앞서 생성한 JWT key 파일의 key 값을 생성한 문자열(nginxstorekey)을 입력합니다.

4. 생성된 JWT 값을 확인합니다.

NGINX API Gateway API Key/JWT - JWT token

5-3. JWT 인증 확인

JWT는 다음과 같이 헤더에 포함하여 요청을 전송할 수 있습니다.

$ curl -H "Authorization: Bearer <JWT>" http://192.168.200.160/api/path2

1. JWT를 포함하지 않은 요청을 전송합니다.

$ curl http://192.168.200.160/api/path2  # NGINX Plus 서버 IP

<html>
<head><title>401 Authorization Required</title></head>
<body>
<center><h1>401 Authorization Required</h1></center>
<hr><center>nginx/1.25.3</center>
</body>
</html>

401 응답 코드를 반환합니다.

$ curl -I http://192.168.200.160/api/path2

HTTP/1.1 401 Unauthorized
Server: nginx/1.25.3
Date: Wed, 26 Jun 2024 01:53:45 GMT
Content-Type: text/html
Content-Length: 179
Connection: keep-alive
WWW-Authenticate: Bearer realm="path2 API"

요청의 헤더를 확인해 보면, WWW-Authenticate 헤더의 값에 설정한 realm을 반환합니다.

2. 유효하지 않은 JWT를 포함한 요청을 전송합니다.

$ curl -H "Authorization: Bearer abc.def.ghi" http://192.168.200.160/api/path2

<html>
<head><title>401 Authorization Required</title></head>
<body>
<center><h1>401 Authorization Required</h1></center>
<hr><center>nginx/1.25.3</center>
</body>
</html>

401 응답 코드를 반환합니다.

3. 생성한 JWT를 포함하여 요청을 전송합니다.

$ curl -i -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjAwMDEifQ.eyJuYW1lIjoic29uZyIsImlzcyI6Imp3dC5pbyJ9.wsZBR84pcKmwdzKXEjFIIjfNHjZrXbH4C38q4DAjQI4" http://192.168.200.160/api/path2

HTTP/1.1 200 OK
Server: nginx/1.25.3
Date: Wed, 26 Jun 2024 05:45:02 GMT
Content-Type: text/plain
Content-Length: 115
Connection: keep-alive

Server address: 192.168.200.174:82
Server name: Path2
URI: /api/path2
Request ID: f22ab765f8105236e50b8700dff6b74b

200 응답 코드와 함께 정상적으로 응답을 반환하는 것을 확인할 수 있습니다.

5-4. JWT claim 활용 접근 제한 설정

NGINX는 map 블록을 사용하여 JWT의 payload에 담긴 특정 claim의 값을 기반으로 변수를 할당할 수 있습니다. 해당 설정을 통해 API Key 인증 예제와 같이 접근 제한을 설정할 수 있습니다.

1. JWT claim에 따른 변수 할당을 위해 jwt_claim.conf 파일을 작성합니다. map 블록을 통해 $allowed_role 변수를 할당했습니다.

# /etc/nginx/auth/jwt_claim.conf

map $jwt_claim_name $allowed_role {  # name claim에 따라 변수를 할당
    default 0;
    "jason" 1;                       # name claim의 값이 1인 경우 allowed_role 변수에 1 할당
}

$jwt_claim_<claim 이름> 과 같은 형식으로 변수를 설정하면, 해당 claim의 값에 따라 변수를 할당합니다. 예제의 경우 ‘name’ claim에 따라 변수를 할당합니다.

2. 작성한 jwt_claim.conf 파일을 api_gateway.conf 파일에 include 합니다.

# /etc/nginx/api_gateway.conf

include /etc/nginx/auth/jwt_claim.conf;

...

3. 접근 제한 설정을 위해 기존에 JWT 인증 설정이 적용된 /api/path2 location 블록에 추가 설정을 합니다.

include /etc/nginx/auth/jwt_claim.conf;

server {
    listen       80;
    server_name  localhost;

    access_log  /var/log/nginx/default_access.log main;
    error_log   /var/log/nginx/default_error.log;

    location / {
            #auth_request /key_auth;
            root   /usr/share/nginx/html;
            index  index.html index.htm;
    }

    location /api/path1 {
            proxy_pass http://192.168.200.174:81;
            #limit_req zone=path1_rate;
            #limit_req_status 429;
    }

    location /api/path2 {
            auth_jwt "path2 API" ;
            auth_jwt_key_file auth/api_secret.jwk;

            if ($jwt_claim_name = "") { # name claim 값이 없으면
                return 401;            # 401 응답 반환
            }

            if ($allowed_role != 1) {  # 할당된 변수의 값이 1이 아닐 경우
                return 403;            # 403 응답 반환
            }

            proxy_pass http://192.168.200.174:82;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

}

위 설정에 따르면 name claim의 값이 jason인 JWT를 포함한 요청만을 백엔드 서버로 프록시합니다.

3. 변경된 설정을 적용합니다.

$ sudo nginx -s reload

4. 요청을 전송해 적용을 확인합니다.

$ curl http://192.168.200.160/api/path2

<html>
<head><title>401 Authorization Required</title></head>
<body>
<center><h1>401 Authorization Required</h1></center>
<hr><center>nginx/1.25.3</center>
</body>
</html>

JWT가 포함되지 않은 경우 401 응답을 반환합니다.

$ curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjAwMDEifQ.eyJuYW1lIjoic29uZyIsImlzcyI6Imp3dC5pbyJ9.wsZBR84pcKmwdzKXEjFIIjfNHjZrXbH4C38q4DAjQI4" http://192.168.200.160/api/path2

<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.25.3</center>
</body>
</html>

기존에 생성한 토큰(name: song)을 포함해 요청 시 403 응답을 반환합니다

$ curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjAwMDEifQ.eyJuYW1lIjoiamFzb24iLCJpc3MiOiJqd3QuaW8ifQ.0decG1D8kYioDBYNZMY7T3g1c1bC5CWbxyx8rlP6MKU" http://192.168.200.160/api/path2

Server address: 192.168.200.174:82
Server name: Path2
URI: /api/path2
Request ID: 49224434bdbfc12d0afba54b783315de

name: jason으로 발행한 토큰을 통해 요청 시 정상적으로 응답합니다.

6. NGINX API Gateway API Key/JWT 결론

이번 포스트에서는 NGINX API Gateway API Key/JWT 인증을 설정하는 방법을 알아봤습니다. API Key/JWT 인증을 통해 API Key/JWT 가 포함되지 않은 요청을 API에 접근하지 못하도록 막고, map 모듈을 활용하여 특정 API Key를 통한 접근 제어 및 JWT 토큰 payload의 claim을 통한 접근 제어를 설정할 수 있었습니다.
이러한 API Key/JWT 설정을 통해 API의 보안을 강화하고, 권한이 없는 사용자의 접근을 효과적으로 차단할 수 있습니다.

NGINX STORE를 통한 솔루션 도입 및 기술지원 무료 상담 신청

* indicates required