카테고리 없음

[정글] WEEK00 미니 프로젝트 (2-1) JWT와 쿠키-세션 인증 방식

쥥이 2024. 12. 24. 16:50


정글 week00주차 프로젝트를 진행할 때 JWT나 HTTPS 등의 기술을 웹 사이트에 적용하기는 했어도 이 기술들에 대해 제대로 공부하고 넘어갈 시간까지는 없었다. 정글을 수료한 지금 여유가 생겼으니 JWT와 HTTPS에 대해 차분하게 정리해보려고 한다. 이번 글은 JWT에 대한 정리 글이다. JWT와 전통적인 쿠키-세션 인증 방식의 차이를 중심으로 JWT에 대해 알아보겠다.

쿠키-세션 인증 방식

전통적인 쿠키-세션 인증 방식에 대해서는 이전(https://manythreedays.tistory.com/60)에 정리한 바가 있다. 쿠키-세션 인증 방식은 어떤 사용자가 로그인 인증을 하고 나면 그 사용자의 정보가 서버에 세션으로 저장되는 방식이다. 각 세션은 고유한 세션 ID로 식별되는데 서버는 이 세션 ID를 쿠키에 담아 클라이언트에게 전달한다. 그러면 클라이언트는 다음에 서버에 요청을 보낼 때마다 발급받은 쿠키를 같이 보낸다. 요청을 받은 서버는 쿠키에 담긴 세션 ID를 확인하고 해당되는 세션에서 사용자의 정보를 조회한다.

쿠키-세션 인증 방식의 문제점

이러한 쿠키 세션 인증 방식의 문제점은 다음과 같다.

  • 기본적으로 하나의 서버의 메모리에 사용자 세션을 저장하기 때문에 서버가 한 대가 아니라 여러 대인 분산 환경인 경우 서버 간 세션 정보의 공유가 어려움
  • 서버에 사용자 세션이 계속해서 쌓이기 때문에 서버의 메모리 사용량이 많음

사실 이 중에서 첫 번째 문제점은 중앙집중식 저장소인 Redis를 사용하면 해결될 수 있는 문제이기는 하다. 그러나 Redis라는 추가적인 저장소를 사용해야 한다는 점은 여전히 한계점이 된다.

JWT 인증 방식

JWT는 JSON Web token의 약자이다. 풀 네임에서 알 수 있듯이 JWT는 JSON이라는 형식에 사용자의 인증 정보를 저장한다.

JWT 인증 방식의 특징 및 장점

쿠키-세션 인증 방식과 비교하여 JWT는 다음과 같은 특징을 가지고 있다.

  • JWT는 self-contained 형태로 사용자의 인증 정보(사용자 이름, 권한 정보, 인증 만료 시간 등)를 내장하고 있음
  • 주로 서버의 저장소가 아닌 클라이언트의 저장소(브라우저의 로컬 스토리지, 세션 스토리지, 메모리 등)에 저장됨

이러한 특징을 가진 JWT는 서버가 여러 대인 경우에도 문제가 없으며 서버의 메모리를 차지하지 않는다는 장점을 가진다. 

 

JWT의 인코딩

JWT는 JSON 형태에 내용을 담고 있기는 하지만 HTTP 프로토콜 메시지에 담길 때 또는 브라우저의 로컬 스토리지에 저장될 때는 Base64 포맷으로 인코딩 Encoded된 형태로 관리된다.

JWT의 인코딩된 버전과 디코딩된 버전

 

JWT 헤더

사용자의 인증 정보를 담고 있는 페이로드 payload 앞뒤에는 추가적인 정보가 붙어있는데 앞에는 헤더 header가 붙는다. 헤더에는 두 가지 정보가 들어간다. 토큰의 타입서명에 사용된 알고리즘 정보이다. 토큰의 타입은 대부분 "jwt"일 테지만 "typ"은 표준 필드이므로 명시해준다. 서명 알고리즘으로는 HS256 (HMAC with SHA-256) 등의 대칭키 알고리즘 또는 RS256 (RSA Signature with SHA-256) 등의 비대칭키 알고리즘을 사용할 수 있다. 

JWT 서명

서명 signature은 페이로드 뒤에 붙는 부분이다. JWT는 쿠키-세션 인증방식에서는 없었던 문제점을 한 가지 갖게 된다. 인증 정보가 서버에 저장되는 게 아니라 클라이언트에 저장되기 때문에 사용자의 인증 정보가 조작될 위험에 노출될 수 있다는 것이다. JWT에는 이러한 문제점을 서명 signature이라는 장치를 통해 해결한다. 서명은 인증 정보를 담고 있는 페이로드 payload가 위, 변조되지 않았음을 증명해 주는 부분이다. 

 

서명에 대칭키 알고리즘을 사용할 경우 JWT 비밀 키, 예를 들면 "my-jwt-secret-key"를 이용해서 메시지 (baseurl64 타입으로 인코딩한 헤더 json + "." + baseurl64 타입으로 인코딩한 페이로드 json)을 암호화한다. 

 

대칭키 알고리즘 HMAC

대칭키 알고리즘의 하나인 HS256 (HMAC with SHA-256)의 경우 비밀 키와 메시지를 가지고 SHA-256 해시 함수를 다음과 같이 적용하여 최종 서명을 생성해낸다. 

 

HMAC(key, message) = hash((key ⊕ opad) ∥ hash((key ⊕ ipad) ∥ message))

  • 는 비트 단위 XOR 연산
  • 는 문자열 연결
  • ipad: 각 바이트가 0x36인 블록 크기만큼의 내부 패딩
  • opad: 각 바이트가 0x5c인 블록 크기만큼의 외부 패딩
  • 내부 해시 계산: K ipad를 XOR 연산한 결과에 메시지 m을 연결한 후, 해시 함수를 적용
  • 외부 해시 계산: K opad를 XOR 연산한 결과에 내부 해시 결과를 연결한 후, 다시 해시 함수를 적용

JWT 동작 방식

로그인 요청을 받은 서버는 사용자 인증을 마친 후에 위에서 설명한 헤더, 페이로드, 서명을 만들고, 각각을 base64url로 인코딩하고, 세 부분을 "."으로 연결하여 JWT를 생성한다. 이 JWT를 전달받은 클라이언트는 다음 요청부터 요청 헤더에 Authorization이라는 키에 대한 값으로 "Bearer <JWT 내용>"을 설정하여 보낸다.

 

클라이언트의 요청을 받은 서버는 요청 헤더에 담겨온 JWT의 헤더와 페이로드 부분을 암호화한 후 JWT의 서명과 비교해본다. 이를 통해 자신이 발급한 JWT 정보에서 내용이 변경된 것이 없는지 확인함으로써 payload의 무결성을 확보한다. JWT 서명은 암호화의 역할을 하는 것이 아니라 페이로드의 무결성과 신뢰성을 확보하는 역할을 하는 것이다. JWT 페이로드에 있는 콘텐츠는 base64url로 인코딩되어 있을 뿐 암호화되지 않기 때문에 누구나 열람할 수 있다. 따라서 민감한 정보는 JWT의 페이로드에 넣지 않도록 주의해야 한다. 

 

JWT 비밀키 관리 방식

JWT 서명에 대칭키 알고리즘을 사용할 경우 필연적으로 따라오는 문제는 '대칭 키 알고리즘에 사용되는 비밀 키를 어디에 저장해둘 것인가'하는 것이다. 코드상에 비밀 키를 올려버리면 보안상 당연히 문제가 된다. 공격자가 비밀 키를 가지고 JWT의 서명도 위조할 수가 있기 때문이다. JWT 비밀 키를 관리하는 방식에는 여러 개가 있다. 가장 간단한 방식은 .env 파일에 비밀키를 저장하고 이 파일이 github에 올라가지 않게 .gitignore에 .env를 등록해놓는 것이다. 대신 이렇게 하면 분산 환경에서 비밀키를 공유하기 어렵다. JWT 비밀키를 관리할 수 있는 다른 방법들은 시간이 나면 또 정리해보겠다.