6 분 소요

로그인 기능을 구현하려면 쿠키, 웹 스토리지, 세션, 토큰이 뭔지 알아야 한다!

1. 쿠키(cookie)

쿠키는 브라우저에 저장되는 작은 크기(최대 4KB)의 문자열이다. 서버는 쿠키를 이용해서 브라우저에 데이터를 넣어준다! 나에 대해 기억하기 위해서..! 웹사이트에 방문하면, 브라우저는 서버에 요청을 보내고, 서버는 이에 응답하게 된다. 이 응답에는 모든 데이터와 내가 찾던 페이지 정보가 있다. 뿐만 아니라, 브라우저에 저장하고자 하는 ‘쿠키’도 있을 수 있다!! 내가 만약 브라우저에 쿠키를 저장한 후, 해당 웹사이트에 방문할 때마다 브라우저는 해당 쿠키도 요청과 함게 보내게 된다.

쿠키는 도메인에 따라 제한된다. 예를 들면, 유튜브가 준 쿠키는 유튜브에만 보내지게 된다. 그리고 쿠키는 유효기간이 있어서 서버가 정한 기간만큼만 유효하다. 쿠키는 인증만 하는 건 아니고, 여러 정보를 저장할 수도 있다. 예를 들어 웹사이트 언어 설정을 바꾸면, 서버는 브라우저에게 쿠키를 주고, 내가 선택한 언어를 저장한다. 그러면 내가 그 다음에 웹사이트에 방문할 때, 쿠키는 요청과 함께 서버로 보내져서 서버는 쿠키가 기억해둔 언어로 페이지를 제공하게 된다!

그런데 쿠키에는 문제가 있다. 보안상의 문제점으로는 CSRF(쿠키가 자동으로 전송된다는 특징을 이용해서 사이트에 로그인이 되어 있는 사용자에게 악성 스크립트를 실행시켜 비밀번호를 변경하거나 결제를 요청하는 등의 악의적인 요청을 하는 것)와 XSS(사용자의 민감 정보 탈취)에 노출되어 있다는 것이다. 또한 저장 용량이 4KB로 부족하고, http 요청 시 자동으로 모든 쿠키를 전송하기 때문에 불필요한 트래픽이 증가한다는 것이다.




2. 웹 스토리지(web storage)

위와 같은 쿠키의 문제점을 해결할 수 있는 웹 스토리지가 HTML5에 등장하게 되었다. 웹 스토리지는 5MB의 저장 용량을 가지고 있어서 쿠키의 부족한 저장 용량 문제를 해결할 수 있다. 또한 요청 시에 자동으로 Headers에 전송되지 않기 때문에 보안상의 문제와 트래픽 문제도 어느 정도 해결할 수 있다. 웹 스토리지도 쿠키와 마찬가지로 문자열만 저장 가능하다는 특징이 있는데, 직렬화를 통해 객체로 저장할 수도 있다. 웹 스토리지에는 두 종류가 있고, 이 둘은 동기적으로 실행된다.


2.1. 로컬 스토리지(local storage)

로컬 스토리지는 도메인 별, 브라우저 별로 독립된 스토리지를 사용한다. 따라서 다른 웹사이트에 들어가거나 브라우저가 변경된다면 로컬 스토리지가 동일하지 않다. 로컬 스토리지는 만료 기간이 없어서, 브라우저를 종료해도 삭제되지 않고, 직접 삭제해야만 삭제된다.


2.2. 세션 스토리지(session storage)

반면, 세션 스토리지는 탭 별, 도메인 별, 브라우저 별로 독립된 스토리지를 사용한다. 즉, 탭만 변경되어도 세션 스토리지가 동일하지 않다. 세션 스토리지는 로컬 스토리지와는 달리 탭만 종료해도 삭제된다. 브라우저가 종료되어 삭제되어도 무관한 데이터는 세션 스토리지에 저장하는 것이 좋다.


2.3. 웹 스토리지 사용 시 고려할 점

  • 웹 스토리지를 사용할 때에는 문자열만 저장이 가능하다. 따라서 객체를 사용하기 위해서는 직렬화/역직렬화 과정이 필수이다. 직렬화를 할 때 순환참조이거나, 역직렬화를 할 때 JSON 형식이 아닌 경우 다음과 같이 에러가 발생하게 된다. web_storage_error

  • 대부분의 브라우저에서 웹 스토리지를 지원하지만, 버전에 따라 지원하지 않을 수도 있다. 혹은 웹 스토리지를 비활성화할 수 있는 설정도 존재하기 때문에(예를 들어, 사파리 시크릿 모드 접속 시, 할당량이 0으로 설정되어 있어 “할당량 초과”라는 에러가 뜬다) 반드시 에러 처리를 해야 한다.


2.4. 웹 스토리지의 문제점

쿠키의 문제점을 어느 정도 해결했지만, 자바스크립트로 접근 가능하기 때문에 여전히 XSS에 취약하다. 또한, 브라우저 간 공유가 되지 않아 디바이스/브라우저/탭(세션 스토리지) 간에 공유가 불가능하다. 쿠키처럼 만료 기간을 설정할 수 없다는 것도 문제점이다. 마지막으로, 웹 스토리지는 동기적으로 실행되기 때문에 메인 스레드를 블로킹할 수 있어서 용량이 크다면 IndexedDB를 사용하는 것이 좋다고 한다!


※ 쿠키와 웹 스토리지 정리

  • 쿠키와 웹 스토리지 모두 보안 문제가 있기 때문에 민감 정보는 저장하지 않는다!
  • 쿠키 사용처: 기간 설정, 작은 용량으로 서버 전송 => n일 동안 보지 않기, 비로그인 장바구니
  • 로컬 스토리지 사용처: 브라우저 종료 시 유지 => 사용자 설정 저장, 글 임시 저장
  • 세션 스토리지 사용처: 탭 종료 시 삭제 ok => 이전 페이지 저장, 이전 스크롤 위치 저장


보통 쿠키를 검색하면 같이 등장하는 것들이 세션과 토큰이다. 왜 세션과 토큰이 필요한지 이해하려면, 우리가 웹사이트를 이용할 때 쓰는 프로토콜인 http가 stateless라는 사실을 기억해야 한다. stateless라는 건, 서버로 가는 모든 요청이 이전의 요청과는 개별적으로, 독립적으로 다뤄진다는 뜻이다. 요청들끼리 연결이 없다(메모리가 없다)는 의미이기도 하다. 요청이 끝나면, 서버는 내가 누군지 잊어버리게 된다. 따라서 요청할 때마다 우리가 누군지 알려줘야 한다. 우리가 누군지 알려주는 방법 중 하나가 바로 세션이다!!




3. 세션(session)

만약 ‘카비’라는 유저명이 있고, 로그인을 하고 싶다면 유저명과 비밀번호를 서버에 보내게 된다. 만약 비밀번호가 맞다면 서버는 세션DB에 ‘카비’라는 유저를 생성한다. 해당 세션에는 별도의 ID가 있는데, 그 세션ID는 쿠키를 통해 브라우저로 돌아오고 저장된다. 따라서, 같은 웹사이트의 다른 페이지로 이동하게 되면, 브라우저는 세션ID를 갖고 있는 쿠키를 서버에게 보낸다. 쿠키는 자동으로 보내지니까! 서버는 세션ID를 가진 쿠키를 확인하게 된다. 그치만 세션ID가 있는 쿠키를 지닌 요청이 있다는 것만 알 뿐, 서버는 아직도 내가 누군지 모른다ㅜ. 서버는 해당 세션ID를 가지고 세션DB를 확인할 것이고, 거기서 해당 ID는 유저명 ‘카비’의 것이라는 걸 알게 되는데, 그제서야 서버는 내가 누군지 알게 된다!! 그때 ‘카비님 환영합니다~!’라는 메시지를 띄울 수 있게 된다. 해당 요청이 끝나고 다른 페이지로 이동하게 되면, 이 모든 프로세스가 반복된다. 잊으면 안 되는 것은, 중요한 유저 정보는 모두 ‘서버’에 있다는 것이다! 유저가 갖고 있는 건 세션ID뿐이다. 쿠키는 그저 세션ID를 전달하기 위한 이동수단일 뿐!! 세션을 이용해서 IOS, 안드로이드 앱을 만들 수 있지만, 쿠키는 네이티브앱에는 없고 브라우저에만 있기 때문에 사용할 수 없다.




4. 토큰(token)

바로 이러한 경우(세션 혹은 세션DB를 사용할 수 없는 경우)에, 토큰을 사용한다! 서버에 토큰을 보내는 것이다. 토큰은 기냥 이상하게 생긴 문자열이라고 한다. 해당 토큰을 서버에 보내고, 서버는 세션DB에서 해당 토큰과 일치하는 유저를 찾는다. 문제는, 서버는 현재 로그인한 유저들의 모든 세션ID를 DB에 저장해야 한다. 즉, 요청이 들어올 때마다 서버는 쿠키를 받아서 세션ID를 보고, 세션ID와 일치하는 유저를 찾고, 그제서야 다음 작업을 수행할 수 있다. 유저가 늘어남에 따라, DB리소스가 더 필요하다는 의미!!!


4.1. JWT(JSON Web Token)

이때 등장하는 게 JWT!! JWT로 인증을 처리하면, 세션DB를 가질 필요도 없고 서버는 유저 인증한다고 위에서처럼 많은 일을 하지 않아도 된다!

세션과 토큰의 차이를 이해하기 위해 로그인을 예로 들면, 우선 유저명과 비밀번호를 서버에 보내는 것까지는 동일하다. 그런데 토큰의 경우 유저명과 비밀번호가 맞다면, 서버는 DB에 뭔가를 생성하지 않는다. 대신에 서버는 (예를 들면) 유저의 ID를 가져다가 사인 알고리즘을 이용해서 ‘사인’을 한다. 그리고 나서 ‘사인된 정보’를 문자열 형태로 보낸다. JWT는 공간 제약이 없어서 쿠키를 보낼 때 사용하는 보통의 세션ID보다 훨씬 길다!(쿠키는 공간 제약이 있기 때문.) 정리하면, 로그인을 할 때 DB를 긁는 대신, 정보를 사인하고 전달하는 게 전부이다. 서버에 요청을 보내려면, 세션ID와 비슷하게 해당 ‘사인된 정보’ 혹은 토큰을 서버에 보내야 한다. 서버는 토큰을 받으면, 해당 사인이 유효한지(조작됐는지) 체크하고, 토큰이 유효하다면 서버는 우리를 유저로 인증해준다~!!

세션에서는 그냥 세션ID만 주면 되고, 세션에 대한 모든 정보는 세션DB에 저장되어 있다. 페이지를 요청하면, 서버는 세션ID를 DB에서 찾으면 되는 것! 반면, JWT에서는 서버는 유저를 인증하는 데 필요한 정보를 토큰에 저장한다! 그리고 해당 토큰을 우리에게 준다. 페이지를 요청하면, 서버는 해당 토큰이 유효한지 아닌지만 검증하면 된다. DB를 긁을 필요가 없다는 것!! 주의할 점은, JWT는 암호화되지 않고, 누구나 열어서 해당 콘텐츠를 볼 수 있다. 따라서, 비밀정보를 JWT 안에 둬서는 안 된다는 것이다.

그래도 다행인 것은, JWT는 누구나 볼 수 있지만 조작할 수는 없다. 처음에 만들어졌을 때 서명된 내용과 비교하기 때문이다(verify signature).

[JWT] https://jwt.io/


※ 세션과 JWT 장단점

  • 세션의 경우, 서버는 로그인 된 유저의 모든 정보를 저장하기 때문에, 새로운 기능들을 추가할 수 있다. 예를 들면, 인스타그램처럼 로그인 된 모든 디바이스를 보여주고 원치 않는 디바이스에서 강제 로그아웃을 할 수 있다. 또한 넷플릭스처럼 계정 공유 숫자를 제한하고, 현재 몇 명이 로그인했고 시청 중인지 알 수 있다. 이러한 모든 기능이 가능한 건 바로, 서버가 로그인 정보를 모두 저장했고, 세션DB가 있기 때문!! 이러한 장점을 누리고 싶다면 DB를 구매하고 유지해야 한다. 유저가 늘면 DB도 같이 커져야 하고..! 비용도 같이 증가하고,, 처리 시간도 증가하고! 그래서 디스크에 저장하는 DB가 아니라, 메모리에 저장하는 (빠르고 저렴한) redis나 memCached의 수요가 많다.

  • JWT를 사용하면, 생성된 토큰을 추적하지 않는다. 서버가 아는 건 ‘토큰이 유효한지 아닌지’ 유효 여부일 뿐이다. JWT를 사용하면 DB를 구매할 필요가 없기 때문에, 강제 로그아웃과 같은 기능들은 구현할 수 없다. 토큰은 만료되기 전까지만 유효하니까! 그래도 DB가 필요 없기 때문에 사용 사례가 많다. 한국에서 코로나19 때문에 한동안 시행되던 QR 체크인이 바로 JWT가 들어간 QR코드이다. JWT를 사용하다가 만약 서비스가 커지고, 유저 계정을 더 잘 관리하고 싶다면 그때 세션으로 옮겨타면 된다!




📌 참고:

댓글남기기