
JWT 인증 설정
이 문서에서는 NGINX Plus 기능인 JWT 인증을 사용하여 웹 리소스의 인증을 제어하는 방법에 대해 설명합니다.
목차
1. 소개
2. 전제 조건
3. API 인증을 위한 NGINX Plus 구성
4. NGINX Plus가 JWT를 검증하는 방법
5. JSON Web Key 파일 생성
6. 하위 요청에서 JWK 가져오기
7. 임의 JWT Claim 유효성 검사
8. 중첩된 JWT 추출
1. 소개
NGINX Plus 를 사용하면 JWT 인증을 사용하여 리소스에 대한 액세스를 제어할 수 있습니다. JWT는 OAuth 2.0 프로토콜 위에 있는 표준 ID 계층인 OpenID Connect 표준의 사용자 정보를 위한 데이터 형식입니다. API 및 Microservices 배포자들도 단순성과 유연성 때문에 표준으로 전환하고 있습니다. JWT 인증을 사용하면 클라이언트가 JSON 웹 토큰을 제공하고 토큰은 로컬 Key 파일 또는 원격 서비스에 대해 유효성이 검사됩니다.
2. 전제 조건
- 네이티브 JWT 지원을 위한 NGINX Plus 릴리스 10(R10)
- 중첩된 JWT Claim 및 보다 긴 서명 Key에 대한 액세스를 위한 NGINX Plus 릴리스 14(R14)
- 원격 위치에서 JSON Web Key를 가져오기 위한 NGINX Plus 릴리스 17(R17)
- 암호화된 토큰(JWE) 지원을 위한 NGINX Plus 릴리스 24(R24)
- 중첩된 JWT, 여러 JSON Web Key Source, 조건 기반 JWT 인증 지원을 위한 NGINX Plus 릴리스 25(R25)
- JWT Key Caching을 지원하는 NGINX Plus 릴리스 26(R26)
- JWT를 생성하는 ID 공급자(IdP) 또는 서비스입니다.
NGINX Plus는 다음 유형의 JWT를 지원합니다.
- JSON Web Signature(JWS) – JWT 콘텐츠는 디지털 서명이 됩니다. 다음 알고리즘을 서명에 사용할 수 있습니다.
- HS256, HS384, HS512
- RS256, RS384, RS512
- ES256, ES384, ES512
- EdDSA (Ed25519 and Ed448 signatures)
- JWE(JSON Web Encryption) – JWT의 콘텐츠가 암호화된 상태입니다. 다음과 같은 콘텐츠 암호화 알고리즘(JWE 헤더의 “enc” 필드)이 지원됩니다.
- A128CBC-HS256, A192CBC-HS384, A256CBC-HS512
- A128GCM, A192GCM, A256GCM
다음과 같은 Key 관리 알고리즘(JWE 헤더의 “alg” 필드)이 지원됩니다. - A128KW, A192KW, A256KW
- A128GCMKW, A192GCMKW, A256GCMKW
- dir – 공유 대칭 Key를 콘텐츠 암호화 Key로 직접 사용
- RSA-OAEP, RSA-OAEP-256, RSA-OAEP-384, RSA-OAEP-512
- 중첩된 JWT – JWE에 포함된 JWS 지원
3. API 인증을 위한 NGINX Plus 구성
NGINX Plus가 여러 API 서버(upstream {}
블록)에 대한 게이트웨이(proxy_pass http://api_server
) 역할을 하며, API 서버로 전달되는 요청은 인증되어야 한다고 가정해 보겠습니다.
upstream api_server {
server 10.0.0.1;
server 10.0.0.2;
}
server {
listen 80;
location /products/ {
proxy_pass http://api_server;
#...
}
}
인증을 위한 JWT를 구현합니다.
1. 먼저 클라이언트에 발급할 JWT를 만들어야 합니다. ID 공급자(IdP) 또는 자체 서비스를 사용하여 JWT를 만들 수 있습니다. 테스트 목적으로 자체 JWT를 만들 수 있습니다.
2. JWT를 허용하도록 NGINX Plus 구성: JWT 인증을 사용하도록 설정하고 인증 영역(예제에서는 “realm”, “API”)도 정의하는 auth_jwt
지시문을 지정합니다.
server {
listen 80;
location /products/ {
proxy_pass http://api_server;
auth_jwt "API";
#...
}
}
NGINX Plus는 Query 문자열 매개변수에서 JWT를 가져올 수도 있습니다. 이를 구성하려면 auth_jwt
지시문에 token=
매개변수를 포함하세요.
#...
auth_jwt "API" token=$arg_apijwt;
#...
3. auth_jwt_type
지시문을 사용하여 signed
(JWS), encrypted
(JWE) 또는 nested
(중첩된 JWT) 중 어떤 유형의 JWT를 사용할지 지정합니다. 지시문의 기본값은 signed
이며, JWS의 경우 지시문을 생략할 수 있습니다.
server {
listen 80;
location /products/ {
proxy_pass http://api_server;
auth_jwt "API";
auth_jwt_type encrypted;
#...
}
}
4. 사용 중인 항목에 따라 JWT 서명을 확인하거나 JWT 콘텐츠를 해독하는 데 사용할 JSON Web Key 파일의 경로를 지정합니다. 이 작업은 auth_jwt_key_file
및/또는 auth_jwt_key_request
지시문을 사용하여 수행할 수 있습니다. 두 지시문을 동시에 지정하면 둘 이상의 Key Source를 지정할 수 있습니다. 지시문을 지정하지 않으면 JWS 서명 확인을 건너뜁니다.
이 시나리오에서 Key는 두 개의 파일, 즉 key.jwk
파일과 keys.json
파일에서 가져옵니다.
server {
listen 80;
location /products/ {
proxy_pass http://api_server;
auth_jwt "API";
auth_jwt_type encrypted;
auth_jwt_key_file conf/key.jwk;
auth_jwt_key_file conf/keys.json;
}
}
이 경우에도 두 개의 Key Source가 있지만, Private Key는 로컬 파일 private_jwe_keys.jwk
에서 가져오고, Public Key는 하위 요청에 포함된 외부 ID 공급자 서비스 https://idp.example.com
에서 가져옵니다.
server {
listen 80;
location /products/ {
proxy_pass http://api_server;
auth_jwt "API";
auth_jwt_type encrypted;
auth_jwt_key_file private_jwe_keys.jwk;
auth_jwt_key_request /public_jws_keys;
}
location /public_jws_keys {
proxy_pass "https_//idp.example.com/keys";
}
}
JWT 모듈에서 최적의 성능을 얻으려면 JWT Key 캐싱을 활성화하는 것이 좋습니다. 예를 들어, 위의 구성에 auth_jwt_key_cache
지시문을 사용하여 1시간 동안 JWT Key 캐싱을 활성화할 수 있습니다. auth_jwt_key_request
또는 auth_jwt_key_file
이 변수를 사용합니다. 동적으로 구성된 경우 auth_jwt_key_cache
를 사용할 수 없다는 점에 유의하세요.
server {
listen 80;
location /products/ {
proxy_pass http://api_server;
auth_jwt "API";
auth_jwt_type encrypted;
auth_jwt_key_file private_jwe_keys.jwk;
auth_jwt_key_request /public_jws_keys;
auth_jwt_key_cache 1h;
}
location /public_jws_keys {
proxy_pass "https_//idp.example.com/keys";
}
}
4. NGINX Plus가 JWT를 검증하는 방법
JWT는 다음 조건이 충족되면 유효한 것으로 간주됩니다.
- 서명을 확인하거나(JWS의 경우) 또는 페이로드를 복호화할 수 있습니다(JWE의 경우)
auth_jwt_key_file
또는auth_jwt_key_request
에 있는kid
(‘Key ID’)와alg
(‘algorithm’) 헤더 필드에서 일치하는 Key를 사용합니다(존재하는 경우). - JWT는 유효 기간 내에 표시되며,
nbf
(“not before”) 및exp
(“expires”) Claim 중 하나 또는 둘 모두에 의해 정의됩니다.
5. JSON Web Key 파일 생성
Key로 서명의 유효성을 검사하거나 데이터를 해독하려면 JSON Web Key(key.jwk
)를 생성해야 합니다. 파일 형식은 JSON Web Key 사양에 정의되어 있습니다.
{"keys":
[{
"k":"ZmFudGFzdGljand0",
"kty":"oct",
"kid":"0001"
}]
}
- 여기서
k
필드는secret
(예제에서는fantasticjwt
)를 기반으로 생성된 대칭 Key(base64url로 인코딩됨)입니다. Secret은 다음 명령으로 생성할 수 있습니다.
$ echo -n fantasticjwt | base64 | tr '+/' '-_' | tr -d '='
ZmFudGFzdGljand0
kty
필드는 Key 유형을 대칭 Key(Octet Sequence)로 정의합니다.kid
(Key ID) 필드는 이 JSON Web Key의 일련 번호를 정의합니다.
6. 하위 요청에서 JWK 가져오기
특히 OpenID Connect를 사용할 때 원격 위치(일반적으로 ID 공급자)에서 JSON Web Key를 가져오도록 NGINX Plus를 구성할 수 있습니다. 하위 요청이 전송될 IdP URI는 auth_jwt_key_request
지시문을 사용하여 구성됩니다.
http {
#...
server {
listen 80;
#...
location / {
auth_jwt "closed site";
auth_jwt_key_request /_jwks_uri; # Keys will be fetched by subrequest
proxy_pass http://my_backend;
}
}
}
URI는 유효성 검사 오버헤드를 피하기 위해 JSON Web Key Set을 캐시할 수 있도록 내부 위치(_jwks_uri
)를 참조할 수 있습니다(proxy_cache
및 proxy_cache_path
지시문). JWT Key 캐싱
을 사용하는 경우에도 부하가 많은 API Gateway의 경우 캐싱을 켜는 것이 좋으며, 이는 JWT Key 캐시가 만료될 때 Key 요청으로 인해 Key 서버가 과부하되는 것을 방지하는 데 도움이 되기 때문입니다.
http {
proxy_cache_path /var/cache/nginx/jwk levels=1 keys_zone=jwk:1m max_size=10m;
#...
server {
listen 80;
#...
location = /_jwks_uri {
internal;
proxy_method GET;
proxy_cache jwk; # Cache responses
proxy_cache_valid 200 12h;
proxy_pass https://idp.example.com/oauth2/keys; # Obtain keys from here
}
}
}
하위 요청에서 JWK를 가져오는 전체 예제입니다.
#
proxy_cache_path /var/cache/nginx/jwk levels=1 keys_zone=jwk:1m max_size=10m;
server {
listen 80; # Use SSL/TLS in production
location / {
auth_jwt "closed site";
auth_jwt_key_cache 1h;
auth_jwt_key_request /_jwks_uri; # Keys will be fetched by subrequest
proxy_pass http://my_backend;
}
location = /_jwks_uri {
internal;
proxy_method GET;
proxy_cache jwk; # Cache responses
proxy_cache_valid 200 12h;
proxy_pass https://idp.example.com/oauth2/keys; # Obtain keys from here
}
}
7. 임의 JWT Claim 유효성 검사
JWT를 확인하는 동안 NGINX Plus는 nbf(“not before”) 및 exp
(“expires”) Claim만 자동으로 유효성을 검사합니다. 그러나 경우에 따라, 특히 애플리케이션 별 또는 프로토콜 수준 Claim을 처리할 때 성공적인 JWT 유효성 검사를 위해 더 많은 조건을 설정해야 하는 경우가 있습니다. 예를 들어, OpenID Connect Core에서는 ID
토큰에 대한 iss
(“issuer”), aud
(“audience”), sub
(“subject”) Claim의 유효성 검사를 요구합니다.
JWT 유효성 검사를 위한 추가 조건은 map
모듈을 사용하여 변수로 설정한 다음 auth_jwt_require
지시문을 사용하여 평가할 수 있습니다.
이 경우에는 다음을 확인합니다.
- 토큰 수신자(Audience)는 API입니다(map rule 1).
- 토큰이 신뢰할 수 있는 신원 공급자에 의해 발급되었는지 확인합니다(map rule 2).
- 관리자를 대신하여 호출되는 API의 범위를 지정합니다(map rule 3).
auth_jwt_require
지시문에서 세 가지 결과 변수의 값을 평가하며, 각 변수의 값이 1이면 JWT가 허용됩니다.
upstream api_server {
server 10.0.0.1;
server 10.0.0.2;
}
map $jwt_claim_aud $valid_app_id { #map rule 1:
"~api\d.example.com" 1; #token issued only for target apps
}
map $jwt_claim_iss $valid_issuer { #map rule 2:
"https://idp.example.com/sts" 1; #token issued by trusted CA
}
map $jwt_claim_scope $valid_scope { #map rule 3:
"access_as_admin" 1; #access as admin only
}
server {
listen 80;
location /products/ {
auth_jwt "API";
auth_jwt_key_file conf/api_secret.jwk;
auth_jwt_require $valid_app_id $valid_issuer $valid_scope;
proxy_pass http://api_server;
}
}
인증 목적과 권한 부여 목적 등 auth_jwt_require
지시문을 여러 번 지정할 수 있는 경우도 있습니다. 오류가 발생하면 401
코드가 표시됩니다. 사용자 정의 오류 코드 403
을 다른 auth_jwt_require
지시문에 할당하면 인증과 권한 부여 사용 사례를 구분하고 해당 오류를 적절하게 처리할 수 있습니다.
location /products/ {
auth_jwt "API";
auth_jwt_key_file conf/api_secret.jwk;
auth_jwt_require $valid_app_id $valid_issuer $valid_scope;
auth_jwt_require $valid_scope error=403;
proxy_pass http://api_server;
}
8. 중첩된 JWT 추출
중첩된 JWT는 JWE에 포함된 JWS 토큰입니다. 중첩된 JWT에서는 JWS의 민감한 정보들이 JWE의 추가 암호화를 통해 보호됩니다.
다음과 같은 이유로 중첩된 JWT를 사용하는 것이 JWE보다 바람직할 수 있습니다.
- JWE의 경우, 대상 애플리케이션/서비스에서 먼저 토큰을 복호화한 다음 서명을 확인해야 합니다. 애플리케이션 측에서 암호 해독 작업을 수행하려면 시간과 리소스가 많이 소요될 수 있습니다.
- 중첩 JWT의 경우, NGINX Plus는 대상 애플리케이션과 동일한 신뢰할 수 있는 네트워크에 상주하므로 NGINX Plus와 애플리케이션 사이에 토큰 암호화가 필요하지 않습니다. NGINX Plus는 JWE를 해독하고 동봉된 JWS를 확인한 후 Bearer Token을 애플리케이션으로 보냅니다. 이렇게 하면 애플리케이션의 JWE 복호화 작업이 NGINX Plus로 Offload됩니다.
- 애플리케이션이 JWE를 지원하지 않는 경우 중첩된 JWT를 사용하면 JWS를 완벽하게 보호할 수 있습니다.
중첩된 토큰을 활성화합니다.
1. auth_jwt_type
지시문을 사용하여 nested
JWT 유형을 지정합니다.
auth_jwt_type nested;
2. 해독된 페이로드($jwt_payload
변수)를 Authorization
헤더에 Bearer Token 값으로 애플리케이션에 전달합니다.
proxy_set_header Authorization "Bearer $jwt_payload";
이 예제는 이전 단계를 하나의 구성으로 요약한 것입니다.
upstream api_server {
server 10.0.0.1;
server 10.0.0.2;
}
http {
server {
listen 80;
auth_jwt "API";
auth_jwt_type nested;
auth_jwt_key_file conf/api_secret.jwk;
proxy_pass http://api_server;
proxy_set_header Authorization "Bearer $jwt_payload";
}
}