코드는 공개하지 않았습니다!

JWT

정의

JSON Web Token의 약자로, 정보를 JSON 개체로 안전하게 전송하기 위한 간결하고 독립적인 방법을 정의한 개방형 표준이다. 상대적으로 크기가 작은 덕분에 URL, POST, 혹은 HTTP 헤더로 빠르게 전송될 수 있다. JWT는 Authorization(권한 허가)에 주로 사용되어, 로그인한 사용자는 이후 Request의 header에 JWT를 포함시켜 보냄으로서 루트, 서비스, 리소스에 대한 접근이 허용된다. 물론 주로 쓰이는 방법일 뿐, JWT가 반드시 Single-Sign-On을 위해 만들어진 기술은 아니다.

등장 배경

기존 authorization은 XML을 기반으로한 SAML(Security Assertion Markup Language) 형식을 이용하였다. 하지만 너무 크고(verbose) 느리다는 단점이 있었다. 따라서 크기가 작아 가볍고 전송이 빠른 JWT를 사용하게 되었다.

쿠키,세션,토큰 세가지 있었다.

https://images.ctfassets.net/cdy7uua7fh8z/4S6xl4Yvi0H1CUrLB69ZtH/e3e3bf1767d2d29563a99cf71cab158d/comparing-jwt-vs-saml2.png

구성

JWT는 Header, Payload, Signature 세 부분으로 구성되는데 각 부분은 .(dot) 으로 구분되어 header.payload.signature의 형태이다. . Header와 payload는 Encoding된다.

Header에는 주로 token의 타입(JWT)과 signing algorithm이 담기는데, 알고리즘으로는 HMAC SHA256이나 RSA가 있다.

Payload

payload는 claim으로 이루어져 있다. 사용자의 정보를 담는다. authorization에서는 다음으로 구성된다.

  • 필수 : path(토큰의 적용 대상 URL 경로) Question…
  • 옵션 : exp(토큰 만료 시간), nbf(토큰 활성 시간), cip(클라이언트 IP 대상)
  • Type
    • Registerd claims : interoperable, useful 클레임을 제공한다. issuer, expiration time, subject, audience 가 있다.
    • Public claims : 요구에 맞게 정의할 수 있으나, 충돌을 방지하기 위해 IANA JSON Web Token Registiry에 정의되거나 충돌 방지 namespace를 포함하는 URI로 정의되어야 한다.
    • Pirvate claims : registered나 public 둘 다 사용하지 않기로 한 양 쪽 간에 정보를 공유하기 위해 설정된 클레임

Signature

header와 payload를 Base64 URL로 인코딩하고 dot(.)로 구분하여 연결한 뒤 Secure key와 함께 알고리즘으로 해시값을 추출하여 생성한다.

검증 원리

클라이언트가 Bearer토큰 이라는 토큰 전달 방식를 사용해서 authorization header에 담아서 JWT를 보낸다. 서버는 여기서 header와 payload를 추출한다. 서버는 자신이 가지고 있는 비밀 키와 header, payload로 signature를 생성한다.

JWT에서 추출한 header와 payload로 만든 signature, JWT에서 추출한 signature를 비교하여 일치하면 검증된 것이고, 일치하지 않는다면 해당 토큰은 유효하지 않은 토큰으로 처리되어 request는 거부된다.

기능들의 코드 구현부와 원리

Exception Filter의 에러 핸들링 통제

BusinessExceptionFlter는 main.ts에 전역으로 적용되어있다. 예외를 포착하면 받은 error에 맞게 로직을 처리한다. BsuinessException 클래스로 custom error를 정의하였고, 이외에는 예외처리 형식을 import하였다.

exception filter : 특정 예외를 잡아 사용자에게 응답을 돌려주거나, 예외를 로깅하거나 하는 등의 처리를 해준다.

CORS를 선택적으로 허용

main.ts의 bootrstrap 내부에 구현되어있음.

CORS(Cross-Origin Resource Sharing)는 서버의 동의가 필요한 cross-origin HTTP 요청을 안전하게 처리할 수 있는 메커니즘이다. CORS는 웹페이지가 다른 도메인의 리소스로 접근하여 HTTP 요청을 보낼 때 쿠키와 같은 인증 정보가 포함되어있으면 pre-flight request 요청을 통해 확인하는 과정을 거치는데, 여기에 JWT 토큰은 쿠키가 아니기 때문에 포함되지 않는다.

corsOption 은 origin 값이 없거나, whiteList에서 발견되지 않거나, localhost 환경이거나, 개발환경이면 true를 반환하고, 그 외에는 거절하는 Error를 발생시킨다.

개발/테스트/운영 환경에 따른 구분된 로깅 옵션

환경변수 env로 알맞은 logger를 설정한다.

로깅 : 애플리케이션 실행 중에 발생하는 이벤트, 에러 메시지 등을 시간 순서대로 기록하는 과정

Swagger에 ID / PW 기반 인증 체계 구축

ConfigService를 이용해 .env.local의 환경변수에서 SWAGGER_ID와 SWAGGER_PW를 가져온다. basicAuth 로 base64로 인코딩하여 http Authorization 헤더에 문자열을 추가한다.

Joi를 통한 env 파일의 schema 검증

Joi를 통해 각 값의 type과 반드시 받아야 하는 값을 정의하였다. ENV를 통해 어떤 환경인지 파악한다.

Joi : Javascript 객체의 유효성 검사를 위한 라이브러리

ConfigModule을 통해 환경변수 관리

configModule은 .env.local 파일을 통해 환경변수들을 불러온다. .env파일 내의 ENV 값을 이용해 알맞은 config.{ENV}.yaml 파일을 읽어 데이터베이스와 http등을 설정한다.

로그인 Request, Response에 대한 명세 구현

swagger는 REST 웹 서비스 개발에서 API 문서를 자동으로 완성해준다. 환경변수를 이용해 받은 Service name을 제목으로 하는 새 문서를 생성한다.

이메일 중복 검사 실행, 이미 존재할 경우 적절한 상태 코드, 메시지가 포함된 에러

입력받은 email 값을 repository에서 찾아서 이미 존재한다면 ‘{해당 이메일} already exist’라는 에러메시지를 반환하고, BAD_REQUEST 처리 한다.

네이티브 모듈과 argon2

Node.js에서 네이티브 모듈은 C나 C++로 작성된 addon을 뜻한다. javascript는 단일 스레드로 실행되지만, 네이티브 모듈을 이용하면 다중 스레드를 사용하거나 스레드 풀을 사용하여 요청을 차리할 수 있어서, 복잡한 계산 등을 위한 성능 개선에 사용한다.

argon2는 보안을 위한 비밀번호 해싱에 사용된 addon이다. 위 코드에서 유저가 입력한 password를 해싱하여 새로운 계정을 생성한다.

에드온 : 기능 확장, 특정 애플리케이션에 종속, 플러그인이라고도 불림

라이브러리 : 코드 재사용성, 다양한 애플리케이션에 사용

verify와 verifyAsync의 차이

Async가 붙으면 같은 기능을 하지만 비동기적이라는 의미이다. ‘동기’는 테스크가 순차적으로 실행되어서, 하나가 끝나야 다른 것이 시작되는 반면, ‘비동기’는 동시에 여러 테스크가 실행된다. 함수 앞에 async가 붙게 되는데, 이러한 비동기 함수는 항상 promise를 반환하며, async 함수 안에 있는 await는 promise가 반환된 이후에 실행된다.

첫 번째 코드는 verifyAsync 라는 비동기적 함수로 accessToken과 refreshToken을 ‘JWT_SECRET`으로 검증한 후 jtiAccess, jtiRefresh로 할당한다. 이 과정이 완료되면 두 토큰을 Blacklist에 추가한다.

두 번째 코드에선 findOne 을 통해 user가 할당되길 기다린 후 순차적으로 verify 가 실행되어 password를 검증하는 코드이다.

Question

  1. JWT payload의 필수사항
    • 공식 document에는 JWT의 payload에는 어떤 정보도 담길 수 있으며 required에 관한 이야기가 없다.
    • 반면 naver cloud platform에 path가 필수여야 한다는 말이 있다. 근데 문맥상 naver의 Content Delevery인 Global Edge를 설명하는 부분이라 해당 기능에서 경우를 말하는 것 같다.
    • JWT 자체에는 필수 사항이 없지만, 우리가 JWT를 authorization의 목적으로 사용할 떄는 path가 포함되야 올바르게 작동된다는 말일까?
    • 답변: 필수는 아니지만 사실상 필수인 것들이 있다.
      • 누가 발행했는가? (iss, issuer)
      • 언제 발행했는가? (iat, issued at)
      • sub : Subject, 토큰을 요청한 유저를 뜻하며 보통 email 주소이다. 이걸 사용하면 추가적인 요청 없이도 유저에 대해 작고 unique한 정보를 주고 받을 수 있다. unique한 특성 덕분에 사용자를 특정할 수 있어서 보안 및 사용자 관리에 더 유리하고, 사용자를 식별하고 관리하는 추가 메커니즘이 필요하다.