JWT (Json Web Token)

JINY KIM·2022년 4월 20일
0

유저 인증(Authentication) 여부를 확인할 때 사용하는 토큰

Authentication vs Authorization

  • Authentication : 사용자 식별 (지금 접속한 사용자 A씨가 정말 A씨가 맞는지를 검사)

  • Authorization : 사용자 권한 인증 (지금 접속한 사용자 A씨가 해당 페이지에 접근할 수 있는 지를 검사)


사용 예시

관리자 페이지에 로그인 하여 유저 삭제를 하는 상황

  1. 관리자 A씨가 자신의 e-mail 과 비밀번호를 사용하여 로그인
  2. 로그인 후 유저 관리 페이지로 이동하여 이동
  3. 삭제할 유저를 선택하고, 삭제 버튼을 눌러 작업 진행

위 경우에서, [1] 에서 로그인 한 결과를 서버가 알고 있어야 [3] 에서 권한 확인(Authorization) 이 가능해진다.
또한, 로그인 결과를 서버가 기억하지 못한다면 매번 사용자가 페이지를 옮기고, 버튼을 누를 때 마다 로그인을 다시 진행해야한다.

서버는 어떻게 로그인 결과를 기억하고 있을까?

서버가 유저 정보를 기억하는 방식은 다양하다. 글의 주제인 JWT 를 포함하여 몇 가지 경우를 기술했다.

Case 1. 매번 로그인을 진행한다

가장 기본적인 방법으로. 매번 요청 할 때마다 'username & password'를 헤더에 첨부하고, 검사한다.


  1. 사용자 로그인 시 입력받은 e-mail(username), password 를 브라우저에 저장한다.
  2. 저장한 정보를 매 요청마다 Header 에 첨부한다.
  3. 서버는 들어오는 요청에 대해 위에 두 필드를 검사하여 관리자 A씨의 정상적인 접근 여부를 검사하여 Authorization 을 진행할 수 있다.

위 방법은 매 요청마다 header 에 유저 인증 정보가 포함되어야 하기 때문에 보안 상 리스크가 있다.
심지어 저장한 유저 정보를 잘못 보관하여 유출이라도 되는 경우에는 끔찍해진다.

HTTPS 를 사용한다면 차라리 괜찮지만, HTTP 연결의 경우에는 절대 사용하면 안되는 방법이다.

매번 서버에서 보안 인증을 진행해야 하는 것도 단점이다.

웹 페이지에 HTTP 사용하면 안된다.

HTTP 연결은 브라우저 -> 서버로 통하는 과정에서 수 많은 중간 기기를 거치게 된다.
정보가 전달되는 중간에 악성 사용자가 내 정보를 볼 수 있는데, 로그인 정보가 담겨있다면 큰 보안 리스크이다.
HTTPS 연결은 전달되는 정보를 암호화하여 요청을 보내는 브라우저와 받는 서버만이 정보를 읽을 수 있는 프토로콜이다.

위와 같은 이유로 가장 간단하지만 단점이 있는 방식이다.

Case 2. 세션에 사용자 로그인 정보를 저장한다.

가장 보편적으로 사용되던 로그인 방식이다. 각 브라우저는 서버로 최초 요청 시 session_id 를 받게 되고, 저장한다.
요청을 보낼 때 마다 session_id 를 첨부하여 서버는 어느 브라우저에서 온 요청인지(session)를 검사할 수 있다.

로그인을 진행 하고 나면, 해당 session 에서 로그인이 되었음을 서버에서 기억한다.
다음 요청부터는, 해당 session 은 이미 인증되었기 때문에 추가적으로 검사를 하지 않아도 된다.

장점

  • 유저의 개인 정보가 로그인 시 한번만 교환되기 때문에 상대적으로 안전하다.
  • 매번 로그인을 진행하지 않아도 되어 서버 리소스가 덜 소요된다.

단점

  • 로그인 되어 있는 사용자 정보를 모두 서버에서 저장하고 있기 때문에 메모리 소모가 크다.
  • session_id 가 탈취당할 수 도 있어 완벽한 방법은 아니다. (기본적으로 HTTPS 를 사용하여 정보를 안전하게 전달해야하고, session_id 는 유출되지 않게 저장해야한다)

가장 큰 문제는 수평 확장(Scale Out) 시 각 인스턴스 별로 세션을 공유하기가 힘들어졌다. 'Redis' 를 사용하여 글로벌하게 session 을 관리 하는 것이 정석이다.

수평 확장된 서버에서의 세션 관리 문제

  1. 첫번째 서버에서 사용자가 로그인
  2. 이후 두번째 서버에 요청 시 로그인 정보를 기억하지 못한다.

Redis 를 통한 세션 공유

  1. 첫번째 서버에서 사용자 로그인
  2. 첫번째 서버가 RedisDB 에 세션 저장
  3. 이후 요청 시 RedisDB 에서 세션 정보를 가져온다.

위 단점(귀찮음) 을 바탕으로 Json Web Token 이 주목받기 시작했다.

Case 3. JWT

Json Web Token 은 암호화(Hash)를 기반으로 한 보안 인증 방식이다.

암호화

암호화란, 정보를 임의의 사용자가 정보를 읽고, 수정할 수 없도록 하는 과정이다.

Hello, World?
Iffmp, xpsme?

위의 문장은 쉽게 읽히지만, 아랫 문장은 쉽지 않다.
아랫 문장의 비밀은 알파벳을 순서에 따라 한 글자씩 미루어 적은 것이다.
(a->b, h->i, e->f)

규칙을 알고 난다면, 아랫 문장을 해석하는 것은 어렵지 않다. 위와 같이 특정한 사용자만이 정보에 접근할 수 있도록 하는 것이 암호화의 핵심이다.

물론, 위 규칙은 너무나 간단해서 좋은 암호화라고 할 수 없다.

단방향 암호화와 복호화

위 문장은 암호화도 가능하며, 규칙을 알고 있다면 원래 문장이 무엇이었는지도 알 수 있다.(한 글자씩 당겨 적으면 평문(PlainText)가 된다. 해당 과정을 복호화라고 한다.

하지만 몇몇 암호화 방법(Algorithm)은 복호화가 불가능하다. 가장 널리 사용되고, JWT 의 기본 암호화 방식인 SHA256의 경우에도 단방향 암호화만 가능하다.

https://emn178.github.io/online-tools/sha256.html
위 페이지에서 sha256 암호화를 테스트 할 수 있다.

한번 암호화된 문장을 다시 평문으로 되돌릴 수 없다면, 정보를 얻어내는 것이 불가능하니 잘못된 방법처럼 느껴질 수 있다. 하지만 단방향 암호화의 특성을 이용하면 JWT 등 여러 방면에서 유용하게 쓰일 수 있다.

제일 대표적인 예시는 DB 에 사용자 비밀번호를 저장하는 경우를 들 수 있다.
물론, 있어서는 안되겠지만. 정보 유출 사고가 나서 DB 가 노출된 경우, 신용카드 정보나 사용자 계정 등 민감한 개인 정보가 유출되는 대형 사고로 이어질 수 있다.

그래서 대부분의 경우에 DB에 평문으로 저장하는 대신 암호화된 문자열을 저장한다.

단방향 암호화의 특징

  • 같은 문자열을, 같은 방식으로 암호화하면 항상 같은 결과를 얻는다.
  • 암호화된 결과가 겹쳐서는 안된다.

예시를 들어보자.


위 페이지에서 apple 이란 단어를 암호화 하면 다음과 같은 문자열이 나온다.


banana 를 해싱하면 다음과 같은 문자열이 나온다. 연관성이라고는 찾을 수 없다.

3b812ac32a1bb634c58c06d055236d55896f69b73dcd83625b7d1b7251574e81
apple1 을 암호화하면 다음과 같은 문자열이 나온다. 역시 연관성은 없어 보인다.
apple2 나 여러 apple과 비슷한 문자열을 입력해도 전혀 다른 결과값이 나온다.

하지만 apple 이란 글자를 암호화하면 언제나 같은 문자열이 나온다.
한 글자라도 달라지면, 완전 다른 문자열이 나오지만 정확히 apple 이란 글자를 암호화하면 언제나 같은 결과를 얻을 수 있다.

3a7bd3e2360a3d29eea436fcfb7e44c735d117c42d1c1835420b6b9942dd4f1b

위와 같은 원리로 비밀번호를 검사할 수 있다.

사용자의 비밀번호가 hello,world라면, 언제나 아래와 같은 문자열이 결과로 나올 것이다.
77df263f49123356d28a4a8715d25bf5b980beeeb503cab46ea61ac9f3320eda

위 과정을 통하여 사용자가 무슨 문자열을 입력했는지는 몰라도
(복호화 불가)

같은 입력을 했다는 사실은 알 수 있게 된다
(같은 방식으로, 같은 문자열을 암호화한 결과는 언제나 일정하다)

하지만 문제가 남아있다. 내가 apple 을 SHA256 알고리즘을 통해 암호화 하더라도, 지구 반대편의 서버 관리자가 암호화를 하더라도 같은 결과가 나온다. 그래서 SHA256 알고리즘을 사용할 때는, SALT 라고 부르는 고유 키를 사용해야한다.

SHA256 알고리즘 + SALT 까지 합하여, 내가 사용한 암호화 방식이 되는 것이다.

암호화에 사용되는 SALT 값은 절대 유출되어서는 안된다.
(JWT 에서는 secretKey 라고 통용된다)

JWT 는 어떻게 이루어져 있을까?

https://jwt.io/
위 페이지를 방문해보면 알 수 있듯이, JWT 는 세 부분으로 나누어져 있고 각 부분은 '.' 으로 구분된다.

첫번째 부분은 헤더 정보를 포함한다
타입이나 암호화 방식 등 JWT 자체를 기술하는 내용이 담겨 있다.

두번째 부분은 사용자가 정의한 내용을 포함한다
로그인한 사용자가 누구인지, 언제 로그인했고 언제 까지 토큰이 유효한지 등의 내용이 담겨있다.
후술하겠지만, 해당 부분에 핸드폰 번호나 이메일 같은 개인 정보를 담는 것은 위험하다. JWT 에서 첫번째와 두번째 부분은 평문이기 때문이다.

세번째 부분은 Signature 이다
위에서 같은 내용을, 같은 방식으로 암호화 한 결과는 언제나 같다는 사실을 알았다.
해당 원리를 이용하여, 첫번째 부분과 두번째 부분을 합쳐서 암호화 한 결과를 세번째 부분에 담는다.

secretKey 를 통해 다른 누구도 아닌 내가 암호화한 정보임을 보장할 수 있게 된다. 그렇기에 JWT 만 가지고도 로그인 한 유저인지를 판별할 수 있다.

JWT는 내가 암호화한 토큰인지를 증명함으로써, 내가 인증해준 유저인지를 확인한다.

정리

JWT 는 세 영역으로 구분되어 있다

  • 첫번째 + 두번째는 토큰의 정보를 평문이다.
  • 세번째 정보는 위 두 정보를 암호화한 결과를 지정한다.
  • JWT 토큰이 들어오면 첫번째+두번째 영역을 합쳐 다시 암호화해본다. 이때 결과가 같다면 JWT 의 정보를 신뢰 할 수 있다.

JWT 를 통해 로그인을 진행한다

  • JWT 의 세번째 영역을 통해 해당 토큰이 서버가 발급한 것임을 증명한다.
  • 세션에 유저 정보를 기억하지 않아도, JWT 의 내용을 신뢰하여 유저의 로그인 했음을 알 수 있다.
profile
엔지니어 지망생

0개의 댓글