컴퓨터네트워크

세션, 토큰, 쿠키, JWT

Recfli 2024. 1. 12. 19:51

 [ 작성 이유 ]

 HTTP 관련 강의를 듣던 중 뭔가 여러 개념들이 종합해서 나오는데, 각각의 차이를 명확하게 알지 못하는 것 같다는 느낌이 들었다. 그래서 여러 유튜브 강의를 듣고 내 나름대로 경험과 내용을 정리해보았다.

 

[ HTTP의 특성 ]

 우선 이 내용을 이해하려면 가장 먼저 이해해야하는 정보는 HTTP의 특성이다. HTTP는 stateless로 서버로 가는 모든 요청이 독립적으로 다뤄진다. 이 말은 클라이언트와 서버사이의 지속적인 connection이 없고 요청이 끝나면 connection도 끝난다는 것이다. 또한 따로 서버 내부에 요청에 관한 메모리를 두어 기록하지 않는다는 것이다. 이 기억해두자.

[ 쿠키 ]

 앞에서 설명한 stateless라는 상태를 유지하지 않는다는 특성을 보완하고 정보를 유지하는 게 사용자의 입장이나 서버의 입장에서 좋다. 만약에 로직마다 해당 URI로 접근을 할 때마다 사용자의 정보를 매번 모두 받아서 서버 측에서 인증하고 그 정보로 처리를 하면 불필요한 반복, DB 접근이 자주 일어나서 서비스 성능 저하로 이어지게 될 것이다. 

 

 이런 문제점을 해결하기 위해서 다음에 필요할 정보를 미리 사용자의 웹 브라우저에 가져다 놓고 요청이 올 때 꺼내 쓸 수 있게 해주는 것이 쿠키이다. 쿠키의 작동과정은 4개의 과정으로 설명이 가능하다. 

1. 쿠키없는 Request가 클라이언트로부터 서버로 감.

2. 서버는 유효한 Request이면 해당 유효한 request를 받아 쿠키가 없는 로직을 실행해서 cookie를 생성한 뒤 cookie와 원래 HTTP 메시지를 클라이언트에게 보냄.

3. 클라이언트는 쿠키를 본인의 웹 브라우저에서 저장을 하고 만약에 시간이 지나서 cookie 설정을 보고 server로 보내야 할 상황이라면 cookie와 request를 같이 서버에 보냄.

4. 쿠키정보를 서버가 받으면 그 정보가 있을 때의 로직을 돌려서 시행을 함.

 

 대충 과정이 이렇게 된다. Cookie의 설정과 관련된 부분은 spring에서는 Controller에서 설정이 가능하고 유효기간, 도메인 같은 정보도 찾아보면 설정이 가능하다. 그리고 쿠키로 보내는 정보는 단순하게 Key-Value이고 단순한 String 값이고 쿠키는 용량 제한이 있어 큰 정보를 보낼 수가 없다.

 

 추가적으로  쿠키로 보낼 수 있는 정보는 외부의 공격자로부터 갈취당해도 문제가 없는 정보들로 보내는 게 좋다.

[ 세션과 토큰 ]

 위의 쿠키에 관한 정보가 이해가 됐다면 이제는 세션과 토큰에 관해서 이해를 해보자. 세션과 토큰은 사용자에 대한 인증과 인가 과정에서 필요하다. 인증과 인가의 차이는 간단하게 인증은 Authorization으로 로그인 과정으로 생각하면 되고 사용자 정보가 맞으면 세션이나 토큰을 보내줄 것이다. 인가는 Authentication으로 권한과 관련이 있는데 운영자와 일반 유저 사이에서 일반 유저가 운영자 전용 페이지에 접근하려고 하면 권한이 없습니다하고 거절하는 걸 의미한다. 세션과 토큰은 이 과정을 다루는 방법이고 방식의 차이라고 생각하면 된다. 이제 둘에 관해서 각각 알아보자.

[ 세션 ]

  세션은 서버에 책임이 많은 인증 방식이다. 세션 방식으로 작동하는 서버는 따로 Session DB를 가지고 있는데 서버는 사용자로부터 로그인과 정보를 받아서 로그인에 성공하면 세션 DB에 저장하고 싶은 내용들을 저장하고 Session Id라는 걸 따로 만들어서 해당 정보를 찾을 수 있게 만든 뒤 Session Id만 Cookie에 담아서 클라이언트에게 보내게 된다. 이 때 세션으로 기록하고 싶은 내용들은 모두 서버측 Session DB에 있다. 여기서 쿠키는 그냥 이 Session Id를 보내기 위한 매개체에 불과하다는 걸 알 수 있다. 그림으로 표현하면 아래와 같다. 

 

 이 방식의 장점과 단점은 무엇일까?

 

장점: 세션 방식에서는 다양한 구현을 할 수 있게 만들어준다. 로그인을 하면 Session DB에 접속 내용이 기록이 되는데 이를 통해서 여러 기기에서 동시에 접근을 하는 것을 막는다던지, 아니면 원치 않는 사용자를 Session DB에서 지워서 본인이 가진 Session Id를 사용하지 못하게 하는 구현을 가능하게 해준다.

 

단점: 서버 사이드에서 더 많은 책임을 요구하고 사용자가 늘어날 때마다 Session DB에 더 많은 정보를 담아야 해서 증설해야한다. 일반적인 경우 Session DB를 거치고 본인 어플리케이션 로직에 필요한 DB를 또 거치는 과정을 거쳐야 한다. 결과적으로 느리고 비싸다. 그래서 속도 향상을 위해 Session DB를 일반적인 DB가 아닌 Redis 같은 In-memory 스토리지를 사용한다. 

 

 장점으로 인해서 인터넷 강의라던지 계정 공유에 관해서 철저하게 살펴야하고 동시 접속 같은 걸 불가능하게 해야하는 서비스류에서는 아주 좋은 사용 방식이다.

[ 토큰 ]

 토큰은 굳이 세션 방식처럼 Session DB를 갈 필요없게 하기 위한 방식이다. 그리고 세션과 다르게 클라이언트가 유효한 정보를 모두 저장하고 있다. 토큰 방식에서는 로그인이 성공되면 서버 측에서는 필요한 정보에 유효한 토큰임을 알리는 signature를 한 뒤 암호화해서 토큰에 담고 클라이언트에게 토큰을 보통 Json 형식으로 준다. 그리고 클라이언트는 그 받은 토큰 정보를 잘 기억하고 있다가 나중에 서버에게 요청을 할 때마다 요청과 토큰 정보를 같이 보낸다. 그러면 서버측에서는 Session DB도 어차피 없지만 기존 Session 방식과 다르게 이걸 확인할 필요도 없고 단순히 Session의 유효성만 검증하고 내부로부터 토큰 내부로부터 데이터를 가져와서 핵심 로직을 실행한다. 

 

 그럼 JWT는 왜 제목에 적어놓고 설명을 안했냐라고 하면 이제 하려고 했다. JWT는 signature, 암호화, 토큰 유효성 검사 같은 일들을 해주는 것이다. Spring Security를 해보면 알겠지만 JWT로 인증방식을 사용하면 서버 측에서는 따로 secretKey와 암호화 방식을 선택할 수 있다. 그리고 따로 validation 방식을 정해진 방식에 따라 상속 받아서 만들어주면 이 과정을 쉽게 할 수 있다. 그러면? 세션보다 토큰이 좋은 게 아닐까라고 이야기를 할 수 있지 않을까? 이제 토큰 방식의 장단점을 알아보자.

 

 토큰 방식에서의 장점은 명확하다 굳이 Session DB를 거칠 필요가 없이 유효하기만 하면 해당 정보를 꺼내서 빠르게 로직을 돌릴 수 있어서 빠르고 싸다. 

 

 하지만 문제는 세션의 장점에 있다. 세션은 Session DB에 따로 저장을 하기 때문에 내가 롤을 하고 있는데 내 동생이 내 아이디에 챔피언이 많다고 접속을 하면 Session DB에서 접속 중인데 다른 사람은 왜 들어와하고 쫓아낼 수 있다. 그런데 토큰에서는 유효하기만 하면 이런 걸 신경쓰지 않는다. 그렇기 때문에 장점에 비해서 이런 지속적인 플레이가 필요한 게임 같은 서비스에서는 좋지 않은 방식이다.

[ 보안 ]

 이제 세션과 토큰에 대해서 알아보았다. 정확하게 보안에 관해서는 초보 수준이지만 토큰 방식에서의 문제점이 있다. 서버측에서 토큰 방식은 JWT를 이용해 암호화를 하더라도 인증받은 토큰 정보를 그대로 복사해서 아무데서나 가져다가 줘도 마치 로그인한 것처럼 사용할 수 있다. 이건 어쩔 수 없다. 그냥 Https로 보내는 전체 내용을 암호화해서 보내거나 네트워크 내에서 패킷이 갈취당하지 않아야된다. 이 방식에 대해서 그나마 대응할 수 있는 방법이 있다. 바로 Token을 두 개로 운영하는 것이다. Refresh Token과 Access Token을 이용하는 방식이다.

 

 Access Token은 마치 세션이나 기존 토큰처럼 로그인 후 받아서 요청마다 보내는 토큰이다. 대신 유효 시간이 짧다. 그렇기 때문에 갈취를 당해도 시간이 매우 짧기 때문에 갈취당해도 갈취당하자마자 즉시 사용하는게 아니면 만료돼서 사용할 수 없게 된다.

 

Refresh Token은 Access Token보다 유효기간이 길다. 요청 때마다 보내지 않아서 갈취 당할 가능성이 낮다. 그래서 클라이언트가 Access Token을 보냈다가 인증기간이 만료되면 서버측에서는 클라이언트에게 에러 코드와 Location을 보내준다. 클라이언트는 Location에 맞게 Refresh Token만 보내서 Access Token과 Refresh Token을 재발급 받게 된다.

 

 이 과정을 경험했던 바로 정리하면 다음과 같다. 클라이언트가 서버로 로그인 요청을 보내면 서버는 유효한 정보일 때 ResponseBody에 Json 타입으로 클라이언트에게 AccessToken과 RefreshToken을 보낸다. 그러면 그 정보를 잘 저장하고 있다가 요청이 있을 때마다 Authorization 헤더에 AccessToken을 담아서 보낸다. 그러면 서버측에서는 해당 AccessToken이 유효한지 검증을 하고 변조가 있다면 에러를, 시간 초과라면 RefreshToken을 Authorization에 다시 담아서 보낸 뒤 재발급 받으라는 메시지를 클라이언트에게 보낸다. 그러면 클라이언트는 RefreshToken을 보냄으로써 다시 재발급받고 다시 원하는 요청을 보내면 된다. 그림으로 나타내면 아래와 같다.

 

 이러한 방식으로 나름의 보호가 가능한데 이 과정에 대한 추가적인 로직 비용이 발생한다는 문제점이 있다!

[ 전체 정리 ]

 전체적으로 정리를 해보면 쿠키는 단지 세션과 같은 정보를 보내기 위한 방법이고 그 정보에 관해서 만료시간, 도메인 설정 같은 추가적인 기능을 제공해주는 매개체이다. 그리고 세션과 토큰은 유저의 정보 같은 걸 기록을 하는 방식이다. 세션은 Session DB를 따로 서버에 두고 Session Id만 쿠키에 담아서 클라이언트에게 보낸 뒤 매번 요청마다 확인하는 방식이고 토큰은 따로 서버 없이 signature와 필요 정보를 클라이언트에게 보내고 해당 정보를 요청마다 받아서 확인하는 방식이다. 장점과 단점 부분, 추가적으로 고려해야될 AccessToken과 RefreshToken 내용은 위의 내용을 다시 보기 바란다.

[ 참고 자료 ]

https://www.youtube.com/watch?v=gA1KsJ2ak10

https://www.youtube.com/watch?v=XgcCkcKGbys

https://www.youtube.com/watch?v=tosLBcAX1vk

https://raonctf.com/essential/study/web/cookie_connection

https://tansfil.tistory.com/58

https://tansfil.tistory.com/59