Lsiron

JWT 토큰이란? 그리고 쿠키에 토큰을 담는 방식에 관하여 본문

개발일지/NUDDUCK

JWT 토큰이란? 그리고 쿠키에 토큰을 담는 방식에 관하여

Lsiron 2024. 10. 11. 01:39

내가 프로젝트에서 로그인을 구현할 때 항상 사용했던 JWT 토큰 방식. 이 JWT토큰에 대해서 한번 제대로 파헤쳐 보자.

 

JWT 토큰은 JSON Web Token의 줄임말로, 어떤 사이트나 앱에 로그인했을 때, 그 사이트가 "이 사람이 진짜 맞다!"고 확인해 주는 신분증 같은 거라고 생각하면 되겠다. 그리고 이 신분증에는 비밀이 담겨 있어서 아무나 바꾸지 못 한다.

 

JWT 토큰은 세 가지 부분으로 나눠져 있다. 비유하자면 햄버거랑 비슷하다고 생각하면 되겠다.

// 헤더 (Header)
{
  "alg": "HS256",  // 사용하는 암호화 알고리즘
  "typ": "JWT"     // 토큰 타입
}

// 페이로드 (Payload)
{
  "userId": 123,   // 사용자 ID
  "role": "user"   // 사용자 역할
}

// 서명 (Signature)
"Hs8d9HdsU1vs93hFJdk9H2df9F3r"  // 서명 부분 (보안)

 

 

1. Header (헤더) - 이건 햄버거의 빵 부분이다. 토큰이 어떤 형식으로 암호화되었는지 알려주는 부분이다. 이 부분은 토큰이 어떤 암호화 알고리즘(HS256)을 사용하고 있으며, 토큰 타입이 JWT라는 정보를 담고 있다!

 

2. Payload (페이로드) - 이건 패티. 여기에 로그인한 사람의 정보(예: 사용자 ID나 이름)가 담겨 있다. 예를 들어, userId가 123이고, role이 'user'(일반 사용자)라고 되어 있는 것.

 

3. Signature (서명) - 이건 소스. 이 부분이 토큰이 진짜라는 걸 증명해 주는 비밀 소스라서, 아무나 바꿀 수 없다. 이 부분은 긴 문자열로 되어 있는데, 보안을 위해 암호화된 부분이다. 이 서명 덕분에 토큰이 변조되지 않았다는 것을 증명할 수 있다!

 

실제 토큰은 이 모든 정보를 하나로 합쳐서 문자열로 만들어지는데, 대략 아래와 같이 생겼다.

 

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEyMywicm9sZSI6InVzZXIifQ.Hs8d9HdsU1vs93hFJdk9H2df9F3r​

 

그렇다면 JWT 토큰의 장점과 단점은 무엇일까?

 

장점

  • 간편함: 서버가 사용자 정보를 따로 기억하지 않아도 된다! 이 말은 서버에 세션을 저장하지 않아도 된다는 것! 서버는 상태(세션)를 관리하지 않는 stateless(무상태) 방식으로 작동한다. 즉, 사용자가 로그인할 때 받은 토큰만 있으면 되는데 마치 놀이공원에서 입장권을 한 번 받으면, 그걸로 계속 놀이기구를 탈 수 있는 것과 같다.
  • 빠름: 토큰만 확인하면 되니까 서버가 사용자를 계속해서 인증하는 데 시간이 많이 걸리지 않는다.
  • 범용성: 웹사이트뿐만 아니라 앱에서도 같은 방식으로 쓸 수 있다! 마치 여러 곳에서 사용할 수 있는 만능 티켓처럼.

단점

  • 길다: JWT 토큰은 여러 정보를 담고 있어서 좀 길다. 그래서 데이터를 주고받을 때 용량이 커질 수 있다!
  • 취소 불가: 한 번 발급된 토큰은 그 유효 기간 동안 취소하기 어렵다. 예를 들어, 놀이공원 티켓을 한 번 발급하면 티켓을 없애거나 중간에 바꾸기 어려운 것처럼..
  • 보안 위험: 만약 누군가가 토큰을 훔쳐 가면 그걸로 다른 사람인 척할 수 있다! 그래서 토큰을 저장할 때도 안전한 장소(예: 보안이 강화된 쿠키)에 보관해야 한다! => 내가 프로젝트를 진행할 때 엑세스 토큰과 리프레시 토큰을 쿠키에 담은 이유이다.

이렇게 JWT는 마치 놀이공원 티켓처럼, 사용자에게 쉽게 입장권을 주고, 서버가 그걸 기억하지 않아도 되는 편리한 방식이지만, 그 티켓을 잘 관리해야 한다.

 

그렇다면! 서버와 클라이언트 간의 인증을 처리하는 방법 즉, JWT 프로세스는 어떻게 진행될까?

 

1. 로그인 요청 (Client → Server)

클라이언트(사용자)가 로그인할 때, 아이디와 비밀번호 같은 자격 증명 정보를 서버로 보낸다. 이 단계에서는 사용자가 누구인지 확인하기 위해 로그인 정보를 입력하는 것.

놀이공원에 가면 입장할 때 티켓을 사야 한다. 여기서 클라이언트는 놀이공원에 입장하려는 사람이고, 서버는 티켓을 파는 직원이다!

 

2. JWT 토큰 발급 (Server → Client)

서버는 클라이언트가 보낸 로그인 정보가 맞는지 확인한 후, 맞으면 JWT 토큰을 생성한다. 이 토큰에는 사용자의 정보(예: 사용자 ID, 역할)가 담겨 있고, 보안을 위해 서명까지 되어 있다! 서버는 이 JWT 토큰을 클라이언트에게 응답으로 보낸다!

티켓 판매 직원이 "확인했어! 이제 입장할 수 있어!" 하면서 티켓을 준다! 이 티켓에는 누가 이 티켓을 샀는지 정보가 적혀 있고, 직원의 도장(서명)도 찍혀 있다!

 

3. 토큰 저장 (Client)

클라이언트는 받은 JWT 토큰을 브라우저의 쿠키로컬 스토리지에 저장한다! ( 나는 쿠키에 저장했었음. )

토큰은 사용자가 다시 로그인할 필요 없이 서버에 요청할 때 사용할 수 있는 신분증 같은 역할을 한다!

클라이언트는 티켓을 잃어버리지 않게 잘 보관해야한다! 주머니에 넣거나 가방에 넣어서 놀이기구를 탈 때마다 보여줄 준비를 하는 것!.

 

4. 요청 시 토큰 포함 (Client → Server)

클라이언트가 서버에 새로운 요청을 보낼 때(예: 게시물 조회), 저장된 JWT 토큰을 함께 보낸다! 마치 신분증을 다시 보여주는 것처럼 서버에게 "나 이 사람 맞아요!"라고 알려주는 것.

놀이기구를 탈 때마다 직원에게 티켓을 보여준다. 티켓이 있으면 "오케이, 이 사람은 입장 가능해!" 하면서 놀이기구를 탈 수 있게 해주는 것.

 

5. 서버에서 토큰 검증 (Server)

서버는 클라이언트가 보낸 JWT 토큰이 올바른지 확인한다. 이 토큰이 유효하고 변조되지 않았는지 서명(Signature)을 검사해서 확인. 만약 유효하다면, 토큰 안에 있는 사용자 정보를 바탕으로 요청을 처리한다!

직원은 티켓이 진짜인지 가짜인지 확인해요. 티켓에 적힌 정보와 도장이 진짜라면, 놀이기구를 탈 수 있게 해주는 것!

 

6. 응답 (Server → Client)

서버는 요청이 유효하고 토큰이 맞다면, 클라이언트가 요청한 데이터나 결과를 응답으로 보내준다!

직원이 티켓을 확인하고 나서 "좋아, 놀이기구 타도 돼!"라고 말한다!

 

JWT 비유로 정리 해 놓으니.. 이 간단한 것을 참 어렵게도 풀어놓았길래 한번 블로그에 끄적여본다...🤦

 

그러면 이제 Access Token과 Refresh Token에 대해서 알아보자.

 

엑세스 토큰 (Access Token)

  • 주된 역할: 사용자가 서버에 요청할 때 인증을 해주는 토큰이다! 이 토큰을 서버에 보낼 때 서버는 "이 사용자가 인증되었구나"를 확인하고 요청을 처리한다!
  • 짧은 유효기간: 보통 짧은 기간( 몇 분에서 한 시간 정도 )만 유효하다. 짧은 이유는 만약 엑세스 토큰이 유출되더라도 피해를 최소화하기 위함.
  • 단점: 짧은 유효기간 때문에, 토큰이 만료되면 사용자가 다시 로그인해야 하는 불편함이 있다. 그래서 이를 보완하기 위해 리프레시 토큰이 있는 것!

리프레시 토큰 (Refresh Token)

  • 주된 역할: 엑세스 토큰이 만료되었을 때, 새로운 엑세스 토큰을 발급받기 위해 사용하는 토큰이다!
  • 긴 유효기간: 리프레시 토큰은 더 긴 기간 동안 유효하다. ( 보통 며칠, 몇 주 또는 몇 달 ) 만약 엑세스 토큰이 만료되더라도, 리프레시 토큰을 통해 새로운 엑세스 토큰을 받을 수 있음.
  • 안전한 저장: 리프레시 토큰은 보통 서버나 DB에 저장하고, 엑세스 토큰만큼 자주 노출되지 않게 관리한다. 

왜 이원화할까?

  1. 보안 강화: 엑세스 토큰이 유출되더라도 짧은 시간 내에 만료되므로 피해를 줄일 수 있다! 리프레시 토큰은 자주 노출되지 않고, 서버에서 안전하게 관리되기 때문에 보안이 더 강화된다.
  2. 사용자 경험 향상: 엑세스 토큰이 만료되더라도 사용자가 매번 다시 로그인하지 않아도 된다. 리프레시 토큰을 통해 자동으로 새로운 엑세스 토큰을 발급받아 원활한 사용 경험을 제공할 수 있다!

나 같은 경우에는 엑세스 토큰과 리프레시 토큰을 각각 쿠키에 저장하고 리프레시 토큰은 db에도 저장을 했다.

 

리프레시 토큰을 쿠키에 저장하는 방식은 보안적인 위험이 존재한다. 일반적으로 리프레시 토큰은 서버나 DB에 저장하는 것이 더 안전한데, 그 이유는 리프레시 토큰이 유출되면 해커가 이를 이용해 새로운 엑세스 토큰을 계속 발급받아 사용자의 권한을 악용할 수 있기 때문이다!

 

따라서, 리프레시 토큰을 클라이언트 측에 쉽게 접근할 수 있는 장소(쿠키)에 저장하지 않는 것이 일반적이다.

 

그럼에도 불구하고, 리프레시 토큰을 쿠키에도 저장한 이유는, 클라이언트 측의 편리성에 중점을 두었기 때문이다!

 

누떡 프로젝트의 경우 짧은시간 기획이 방대하여 프론트 측에서 토큰 관련 이슈까지 다루기에는 시간적으로 무리가 있었다.

 

따라서 클라이언트는 리프레시 토큰을 따로 관리할 필요가 없이 재발급 요청만 하면 서버가 쿠키에 담긴 리프레시 토큰과 DB에 저장된 리프레시 토큰 을 비교하여 편리하게 엑세스 토큰을 재발급 해주도록 해 주었다!

 

보안적인 문제는, HTTP-Only와 Secure 속성을 통해 JavaScript에서 토큰에 접근하지 못하도록 하고, HTTPS 환경에서만 토큰이 전송되도록 설정하여 최소화했다. 이러한 조치를 통해 편리성과 보안 사이에서 균형을 맞추는 방식으로 구현했다!

 

덕분에 프로젝트는 무사히 마쳤지만.. 그래도 손을 볼 필요가 있어보인다! 🤦