NGINX API Gateway – API 정의 및 인증 구현

최신 애플리케이션 아키텍처의 핵심은 HTTP API입니다. HTTP를 사용하면 애플리케이션을 신속하게 구축하고 쉽게 유지 관리할 수 있습니다. HTTP API는 단일 목적 마이크로서비스에서 모든 것을 포괄하는 모놀리스에 이르기까지 애플리케이션의 규모에 관계없이 공통 인터페이스를 제공합니다. HTTP를 사용하면 하이퍼스케일 인터넷 속성을 지원하는 웹 애플리케이션 전달의 발전을 통해 안정적인 고성능 API 전달을 제공할 수도 있습니다.

최고의 고성능 경량 리버스 프록시 및 로드 밸런서인 NGINX는 API 트래픽을 처리하는 데 필요한 고급 HTTP 처리 기능을 갖추고 있습니다. 따라서 NGINX는 API Gateway를 구축하는 데 이상적인 플랫폼입니다. 

이 모범 사례에서는 API Gateway 사용 사례를 설명하고 효율적이고 확장 가능하며 유지 관리하기 쉬운 방식으로 처리하도록 NGINX를 구성하는 방법을 보여줍니다. 프로덕션 배포의 기초를 형성할 수 있는 전체 구성에 대해 설명합니다.

목차

1. Warehouse API 소개
2. NGINX Configuration
3. 최상위 API Gateway 정의
4. 단일 서비스 vs 마이크로서비스 API backend
5. Warehouse API 정의
  5-1. API에 대한 광범위한 정의와 정확한 정의 선택
  5-2. 주요 변경 사항을 처리하기 위해 클라이언트 요청 재작성
6. 오류에 응답하기
7. 인증 구현
  7-1. API Key 인증

  7-2. JWT 인증
8. NGINX API Gateway 시리즈

1. Warehouse API 소개

API Gateway의 기본 기능은 백엔드에서 구현되거나 배포되는 방식에 관계없이 여러 API에 대해 일관된 단일 진입점을 제공하는 것입니다. 모든 API가 마이크로서비스 애플리케이션은 아닙니다. API Gateway는 마이크로서비스로 부분적으로 전환되는 기존 API, 모놀리식 및 애플리케이션을 관리해야 합니다.

이 모범 사례에서는 재고 관리를 위한 가상 API인 “Warehouse API”를 참조합니다. 샘플 구성 코드를 사용하여 다양한 사용 사례를 설명합니다. Warehouse API는 JSON 요청을 사용하고 JSON 응답을 생성하는 RESTful API입니다. 그러나 JSON 사용은 API Gateway로 배포될 때 NGINX의 제한 사항이나 요구 사항이 아닙니다. NGINX는 API 자체에서 사용하는 아키텍처 스타일 및 데이터 형식에 구애받지 않습니다.

Warehouse API는 개별 마이크로서비스의 모음으로 구현되고 단일 API로 게시됩니다. 인벤토리 및 가격 책정 리소스는 별도의 서비스로 구현되고 다른 백엔드에 배포됩니다. 따라서 API의 경로 구조는 다음과 같습니다.

api
└── warehouse
    ├── inventory
    └── pricing

예를 들어, 현재 창고 인벤토리를 쿼리하기 위해 클라이언트 애플리케이션은 /api/warehouse/inventory에 HTTP GET 요청을 합니다.

여러 애플리케이션을 위한 API Gateway 아키텍처

2. NGINX Configuration

NGINX를 API Gateway로 사용하는 장점은 리버스 프록시, 로드 밸런서 및 웹 서버 역할을 동시에 수행할 수 있다는 것입니다. 

NGINX가 이미 애플리케이션 제공 스택의 일부인 경우 일반적으로 별도의 API Gateway를 배포할 필요가 없습니다. 그러나 API Gateway에 예상되는 일부 기본 동작은 브라우저 기반 트래픽에 대해 예상되는 동작과 다릅니다. 이러한 이유로 브라우저 기반 트래픽에 대한 기존(또는 향후) 구성에서 API Gateway 구성을 분리합니다.

이러한 분리를 달성하기 위해 다목적 NGINX 인스턴스를 지원하는 구성 레이아웃을 생성하고 CI/CD 파이프라인을 통해 구성 배포를 자동화하기 위한 편리한 구조를 제공합니다. 

/etc/nginx 아래의 결과 디렉토리 구조는 다음과 같습니다.

etc/
└── nginx/
    ├── api_conf.d/ ………………………………… Subdirectory for per-API configuration
    │   └── warehouse_api.conf …… Definition and policy of the Warehouse API
    ├── api_backends.conf ………………… The backend services (upstreams)
    ├── api_gateway.conf …………………… Top-level configuration for the API gateway server
    ├── api_json_errors.conf ………… HTTP error responses in JSON format
    ├── conf.d/
    │   ├── ...
    │   └── existing_apps.conf
    └── nginx.conf

모든 API Gateway 구성의 디렉토리 및 파일 이름에는 api_ 접두사가 붙습니다 . 이러한 각 파일 및 디렉터리는 아래에 자세히 설명된 대로 API Gateway의 다른 기능을 활성화합니다. Warehouse_api.conf 파일은 다양한 방식으로 Warehouse API를 정의하는 아래에서 설명하는 구성 파일에 대한 일반적인 표준입니다.

3. 최상위 API Gateway 정의

모든 NGINX 구성은 기본 구성 파일인 nginx.conf로 시작 합니다. API Gateway 구성을 읽기 위해 nginx.conf 의 http 블록에 게이트웨이 구성이 포함된 파일인 api_gateway.conf (바로 아래 줄 28) 를 참조하는 include 지시문을 추가합니다. 기본 nginx.conf 파일은 include 지시문을 사용하여 conf.d 하위 디렉토리(29행) 에서 브라우저 기반 HTTP 구성을 가져옵니다 . 이 블로그 게시물은 가독성을 돕고 구성의 일부를 자동화할 수 있도록 include 지시문을 광범위하게 사용합니다.

include /etc/nginx/api_gateway.conf; # All API gateway configuration
include /etc/nginx/conf.d/*.conf;    # Regular web traffic

api_gateway.conf 파일은 NGINX를 Client에 대한 API Gatewy로 노출하는 가상 서버를 정의합니다. 이 구성은 API Gateway가 게시한 모든 API를 단일 진입점 https://api.example.com/(9 행)에 노출하며 12~17행에 구성된 대로 TLS로 보호됩니다.

이 구성은 순전히 HTTPS입니다. API Client가 올바른 진입점을 알고 기본적으로 HTTPS 연결을 설정하기를 기대합니다.

include이 구성은 정적입니다. 개별 API 및 해당 backEnd 서비스의 세부 정보는 20행 의 지시문이 참조하는 파일에 지정됩니다. 23행에서 26행은 오류 처리를 다루고 아래 오류에 응답에서 설명 합니다.

include api_backends.conf;
include api_keys.conf;

server {
    access_log /var/log/nginx/api_access.log main; # Each API may also log to a 
                                                   # separate file

    listen 443 ssl;
    server_name api.example.com;

    # TLS config
    ssl_certificate      /etc/ssl/certs/api.example.com.crt;
    ssl_certificate_key  /etc/ssl/private/api.example.com.key;
    ssl_session_cache    shared:SSL:10m;
    ssl_session_timeout  5m;
    ssl_ciphers          HIGH:!aNULL:!MD5;
    ssl_protocols        TLSv1.2 TLSv1.3;

    # API definitions, one per file
    include api_conf.d/*.conf;

    # Error responses
    error_page 404 = @400;         # Treat invalid paths as bad requests
    proxy_intercept_errors on;     # Do not send backend errors to client
    include api_json_errors.conf;  # API client-friendly JSON errors
    default_type application/json; # If no content-type, assume JSON
}

4. 단일 서비스 vs 마이크로서비스 API backend

일부 API는 단일 backend에서 구현될 수 있지만 일반적으로 탄력성 또는 로드 밸런싱을 위해 둘 이상이 있을 것으로 예상합니다. 마이크로서비스 API를 사용하여 각 서비스에 대한 개별 backend를 정의합니다. 함께 완전한 API로 기능합니다. 여기에서 Warehouse API는 각각 여러 backend가 있는 두 개의 개별 서비스로 배포됩니다.


upstream warehouse_inventory {
    zone inventory_service 64k;
    server 10.0.0.1:80;
    server 10.0.0.2:80;
    server 10.0.0.3:80;
}

upstream warehouse_pricing {
    zone pricing_service 64k;
    server 10.0.0.7:80;
    server 10.0.0.8:80;
    server 10.0.0.9:80;
}

API Gateway에서 게시한 모든 API에 대한 모든 backend API 서비스는 api_backends.conf 에 정의되어 있습니다 . 여기에서 각 upstream 블록에서 여러 IP 주소-포트 쌍을 사용하여 API 코드가 배포된 위치를 나타내지만 호스트 이름도 사용할 수 있습니다. NGINX Plus 구독자는 동적 DNS 로드 밸런싱을 활용하여 새로운 backend를 런타임 구성에 자동으로 추가할 수도 있습니다.

5. Warehouse API 정의

Warehouse API는 location 다음 예제와 같이 중첩 구성의 여러 블록으로 정의됩니다. 외부 location 블록( /api/warehouse )은 중첩된 위치가 backend API 서비스로 라우팅되는 유효한 URI를 지정하는 기본 경로를 식별합니다. 외부 블록을 사용하면 전체 API에 적용되는 공통 정책을 정의할 수 있습니다(이 예에서는 6행의 로깅 구성).


# Warehouse API
#
location /api/warehouse/ {
    # Policy configuration here (authentication, rate limiting, logging...)
    #
    access_log /var/log/nginx/warehouse_api.log main;

    # URI routing
    #
    location /api/warehouse/inventory {
        proxy_pass http://warehouse_inventory;
    }

    location /api/warehouse/pricing {
        proxy_pass http://warehouse_pricing;
    }

    return 404; # Catch-all
}

NGINX는 요청 URI를 구성 섹션과 일치시키는 매우 효율적이고 유연한 시스템을 가지고 있습니다. location 지시어의 순서는 중요하지 않습니다. 가장 구체적인 일치 항목이 선택됩니다. 여기에서 10행과 14행의 중첩된 위치는 외부 location 블록 보다 더 구체적인 두 개의 URI를 정의합니다 . 각 중첩 블록의 proxy_pass지시문은 요청을 적절한 업스트림 그룹으로 라우팅합니다. 특정 URI에 대해 보다 구체적인 정책을 제공할 필요가 없는 한 정책 구성은 외부 위치에서 상속됩니다.

중첩된 위치 중 하나와 일치하지 않는 모든 URI는 유효하지 않은 모든 URI에 대한 404 (Not Found) 응답을 반환하는 catch-all 지시문(18행)을 포함하는 외부 위치에서 처리됩니다.

5-1. API에 대한 광범위한 정의와 정확한 정의 선택

API 정의에는 광범위하고 정확한 두 가지 접근 방식이 있습니다. 각 API에 가장 적합한 접근 방식은 API의 보안 요구 사항과 backend 서비스가 잘못된 URI를 처리하는 것이 바람직한지 여부에 따라 다릅니다.

위 의 Warehouse_api_simple.conf 에서 우리는 Warehouse API에 대한 광범위한 접근 방식을 사용하여 접두사 중 하나로 시작하는 URI가 적절한 백엔드 서비스로 프록시되도록 10행과 14행에 URI 접두사를 정의합니다. 이 광범위한 접두사 기반 위치 일치를 사용하면 다음 URI에 대한 API 요청이 모두 유효합니다.

/api/warehouse/inventory
/api/warehouse/inventory/
/api/warehouse/inventory/foo
/api/warehouse/inventoryfoo
/api/warehouse/inventoryfoo/bar/

유일한 고려 사항이 각 요청을 올바른 backend 서비스로 프록시하는 것이라면 광범위한 접근 방식이 가장 빠른 처리와 가장 컴팩트한 구성을 제공합니다. 반면에 보다 정확한 접근 방식을 사용하면 사용 가능한 각 API 리소스에 대한 URI 경로를 명시적으로 정의하여 API Gateway가 API의 전체 URI 공간을 이해할 수 있습니다. 정확한 접근 방식을 취하면 Warehouse API의 URI 라우팅에 대한 다음 구성은 정확한 일치( =)와 정규식 ( )의 조합을 사용하여 ~각각의 유효한 URI를 정의합니다.

 # URI routing
    #
    location = /api/warehouse/inventory { # Complete inventory
        proxy_pass http://warehouse_inventory;
    }

    location ~ ^/api/warehouse/inventory/shelf/[^/]+$ { # Shelf inventory
        proxy_pass http://warehouse_inventory;
    }

    location ~ ^/api/warehouse/inventory/shelf/[^/]+/box/[^/]+$ { # Box on shelf
        proxy_pass http://warehouse_inventory;
    }

    location ~ ^/api/warehouse/pricing/[^/]+$ { # Price for specific item
        proxy_pass http://warehouse_pricing;
    }

이 구성은 더 장황하지만 백엔드 서비스에서 구현하는 리소스를 더 정확하게 설명합니다. 이것은 정규식 일치를 위한 약간의 추가 오버헤드를 희생시키면서 잘못된 Client 요청으로부터 backend 서비스를 보호하는 이점이 있습니다. 이 구성을 사용하면 NGINX는 일부 URI를 허용하고 다른 URI는 유효하지 않은 것으로 거부합니다.

Valid URIsInvalid URIs
/api/warehouse/inventory/api/warehouse/inventory/
/api/warehouse/inventory/shelf/foo/api/warehouse/inventoryfoo
/api/warehouse/inventory/shelf/foo/box/bar/api/warehouse/inventory/shelf
/api/warehouse/inventory/shelf/-/box/-/api/warehouse/inventory/shelf/foo/bar
/api/warehouse/pricing/baz/api/warehouse/pricing
/api/warehouse/pricing/baz/pub

정확한 API 정의를 사용하면 기존 API 문서 형식이 API Gateway 구성을 구동할 수 있습니다. OpenAPI 사양 (이전 의 Swagger)에서 NGINX API 정의를 자동화할 수 있습니다. 

5-2. 주요 변경 사항을 처리하기 위해 클라이언트 요청 재작성

API가 발전함에 따라 엄격한 이전 버전과의 호환성을 깨고 Client를 업데이트해야 하는 변경 작업이 필요한 경우가 있습니다. 이러한 예 중 하나는 API 리소스의 이름을 바꾸거나 이동하는 경우입니다. 웹 브라우저와 달리 API Gateway는 Client에게 새 위치의 이름을 지정 하는 리디렉션 (코드 301 (Moved Permanently))을 보낼 수 없습니다. 다행히 API Client를 수정하는 것이 비현실적일 때 Client 요청을 즉석에서 다시 작성할 수 있습니다.

다음 예에서는 위의 Warehouse_api_simple.conf 에서와 동일한 광범위한 접근 방식을 사용 하지만 이 경우 구성은 가격 책정 서비스가 재고 서비스의 일부로 구현된 이전 버전의 Warehouse API를 대체합니다. 3행 의 rewrite 지시문은 이전 가격 책정 리소스에 대한 요청을 새 가격 책정 서비스에 대한 요청으로 변환합니다.


# Rewrite rules
#
rewrite ^/api/warehouse/inventory/item/price/(.*)  /api/warehouse/pricing/$1;

# Warehouse API
#
location /api/warehouse/ {
    # Policy configuration here (authentication, rate limiting, logging...)
    #
    access_log /var/log/nginx/warehouse_api.log main;

    # URI routing
    #
    location /api/warehouse/inventory {
        proxy_pass http://warehouse_inventory;
    }

    location /api/warehouse/pricing {
        proxy_pass http://warehouse_pricing;
    }

    return 404; # Catch-all
}

6. 오류에 응답하기

HTTP API와 브라우저 기반 트래픽의 주요 차이점 중 하나는 오류가 Client에 전달되는 방식입니다. NGINX가 API Gateway로 배포되면 API Client에 가장 적합한 방식으로 오류를 반환하도록 구성합니다.

최상위 API Gateway 구성에는 오류 응답을 처리하는 방법을 정의하는 섹션이 포함됩니다.

# Error responses
error_page 404 = @400;         # Treat invalid paths as bad requests
proxy_intercept_errors on;     # Do not send backend errors to client
include api_json_errors.conf;  # API client-friendly JSON errors
default_type application/json; # If no content-type, assume JSON

23행 의 error_page 지시문은 요청이 API 정의와 일치하지 않을 때 NGINX가 기본 오류 404 (Not Found) 대신 400Bad Request) 오류를 반환하도록 지정합니다 . 이(선택 사항) 동작을 사용하려면 API Client가 API 설명서에 포함된 유효한 URI에만 요청하고 권한이 없는 클라이언트가 API Gateway를 통해 게시된 API의 URI 구조를 검색하지 못하도록 해야 합니다.

24행 은 백엔드 서비스 자체에서 생성된 오류 를 나타냅니다 . 처리되지 않은 예외에는 Client에 보내고 싶지 않은 스택 추적 또는 기타 민감한 데이터가 포함될 수 있습니다. 이 구성은 표준화된 오류 응답을 Client에 전송하여 보호 수준을 더 추가합니다.

표준화된 오류 응답의 전체 목록은 25행의 include 지시문이 참조하는 별도의 구성 파일에 정의되어 있으며, 그 중 처음 몇 행은 아래에 나와 있습니다. JSON 이외의 오류 형식을 선호하는 경우 이 파일을 수정할 수 있으며 api_gateway.conf의 26행 default_type 값 이 일치하도록 변경됩니다. 또한 각 API의 정책 섹션에 별도의 include 지시문을 사용하여 전역 응답을 재정의하는 다른 오류 응답 파일을 참조할 수 있습니다.


error_page 400 = @400;
location @400 { return 400 '{"status":400,"message":"Bad request"}\n'; }

error_page 401 = @401;
location @401 { return 401 '{"status":401,"message":"Unauthorized"}\n'; }

error_page 403 = @403;
location @403 { return 403 '{"status":403,"message":"Forbidden"}\n'; }

error_page 404 = @404;
location @404 { return 404 '{"status":404,"message":"Resource not found"}\n'; }

이 구성을 사용하면 잘못된 URI에 대한 Client 요청이 다음 응답을 받습니다.

$ curl -i https://api.example.com/foo
HTTP/1.1 400 Bad Request
Server: nginx/1.19.5
Content-Type: application/json
Content-Length: 39
Connection: keep-alive

{"status":400,"message":"Bad request"}

7. 인증 구현

API를 보호하기 위한 인증 없이 API를 게시하는 것은 드문 일입니다. NGINX는 API를 보호하고 API Client를 인증하기 위한 여러 접근 방식을 제공합니다. 일반 HTTP 요청에도 적용되는 접근 방식에 대한 정보는 IP 주소 기반 ACL(액세스 제어 목록), 디지털 인증서 인증 및 HTTP 기본 인증 에 대한 설명서를 참조하십시오 . 여기에서는 API별 인증 방법에 중점을 둡니다.

7-1. API Key 인증

API Key는 Client와 API Gateway가 알고 있는 공유 비밀입니다. API Key는 본질적으로 API Client에 장기 자격 증명으로 발급되는 길고 복잡한 암호입니다. API Key 생성은 간단합니다. 이 예에서와 같이 난수를 인코딩하기만 하면 됩니다.

$ openssl rand -base64 18
7B5zIqmRGXmrJTFmKa99vcit

최상위 API Gateway 구성 파일 api_gateway.conf 의 2행에 api_keys.conf 라는 파일 이 포함되어 있습니다. 이 파일에는 Client 이름이나 기타 설명으로 식별되는 각 API Client에 대한 API Key가 포함되어 있습니다. 다음은 해당 파일의 내용입니다.

map $http_apikey $api_client_name {
    default "";

    "7B5zIqmRGXmrJTFmKa99vcit" "client_one";
    "QzVV6y1EmQFbbxOfRCwyJs35" "client_two";
    "mGcjH8Fv6U9y3BVF9H3Ypb9T" "client_six";
}

API Key는 map 블록 내에서 정의됩니다. map 지시문은 두 개의 매개변수를 사용 합니다. 첫 번째는 API Key를 찾을 위치를 정의합니다(이 경우 변수 apikey에 캡처된 클라이언트 요청의 HTTP 헤더) . $http_apikey두 번째 매개변수는 새 변수( $api_client_name)를 만들고 첫 번째 매개변수가 키와 일치하는 행의 두 번째 매개변수 값으로 설정합니다.

예를 들어 클라이언트가 API Key 7B5zIqmRGXmrJTFmKa99vcit를 제공하면 $api_client_name 변수가 client_one로 설정됩니다. 이 변수는 인증된 클라이언트를 확인하는 데 사용할 수 있으며 더 자세한 감사를 위해 로그 항목에 포함될 수 있습니다. 블록 형식은 기존 자격 증명 저장소에서 api_keys.conf 파일 map을 생성하는 자동화 워크플로에 간단하고 쉽게 통합됩니다 .

여기 에서 인증 결정을 지정된 위치에 위임하는 auth_request 지시문을 정책 섹션에 포함 하도록 “광범위한” 구성( Warehouse_api_simple.conf )을 수정하여 API Key 인증을 활성화합니다.

# Warehouse API
#
location /api/warehouse/ {
    # Policy configuration here (authentication, rate limiting, logging...)
    #
    access_log /var/log/nginx/warehouse_api.log main;
    auth_request /_validate_apikey;

    # URI routing
    #
    location /api/warehouse/inventory {
        proxy_pass http://warehouse_inventory;
    }

    location /api/warehouse/pricing {
        proxy_pass http://warehouse_pricing;
    }

    return 404; # Catch-all
}

예를 들어 auth_request 지시문(7행)을 사용하면 OAuth 2.0 토큰 내부 검사 와 같은 외부 인증 서버에서 인증을 처리할 수 있습니다 . 이 예제에서는 대신 /_validate_apikey 라는 location 블록 형식으로 최상위 API Gateway 구성 파일 에 API 키를 검증하는 로직을 추가합니다 .

    # API key validation
    location = /_validate_apikey {
        internal;

        if ($http_apikey = "") {
            return 401; # Unauthorized
        }
        if ($api_client_name = "") {
            return 403; # Forbidden
        }

        return 204; # OK (no content)
    }

30행의 internal 지시문은 이 위치에 외부 클라이언트가 직접 액세스할 수 없음을 의미합니다( auth_request 에서만 ). 클라이언트는 apikey HTTP 헤더에 API Key를 표시해야 합니다. 이 헤더가 없거나 비어 있는 경우(32행), 클라이언트에게 인증이 필요하다는 401 (Unauthorized) 응답을 보냅니다. 35행은 API Key가 map 블록의 키와 일치하지 않는 경우(이 경우 api_keys.conf 의 2행에 있는 $api_client_name 매개변수 가 빈 문자열로 설정 됨)를 처리 하고 클라이언트에게 인증이 실패했음을 알리는 403 (Forbidden) 응답을 보냅니다.. 이러한 조건 중 어느 것도 일치하지 않으면 API Key가 유효하고 위치에서 204 (No Content) 응답을 반환합니다.

이 구성을 사용하여 Warehouse API는 이제 API 키 인증을 구현합니다.

$ curl https://api.example.com/api/warehouse/pricing/item001
{"status":401,"message":"Unauthorized"}
$ curl -H "apikey: thisIsInvalid" https://api.example.com/api/warehouse/pricing/item001
{"status":403,"message":"Forbidden"}
$ curl -H "apikey: 7B5zIqmRGXmrJTFmKa99vcit" https://api.example.com/api/warehouse/pricing/item001
{"sku":"item001","price":179.99}

7-2. JWT 인증

JSON 웹 토큰(JWT)은 API 인증에 점점 더 많이 사용됩니다. 기본 JWT 지원은 NGINX Plus 전용이며, Best Practices의 NGINX Plus API Gateway로 API Client JWT 인증에 설명된 대로 JWT의 유효성을 검사할 수 있습니다. 

8. NGINX API Gateway 시리즈

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

* indicates required