본문 바로가기

개발

(HTTP)세션과 토큰의 차이

반응형

이번에 개발을 하면서 로그인과 전반적인 인가에 대해 고민하게 됐다. 이미 구현된 사이트에서 기능을 개발할 일만 있었고 Node.js로 해본 것도 처음이라서 이참에 정리를 제대로 하고 가자 싶어서 공부를 했다. 

인터넷 기반 서비스에서는 허용된 사용자만 서비스에 접근을 할 수 있어야한다. 아이디와 패스워드를 이용해 로그인을 하는 방식을 사용하는데 이 과정을 인증(Authentication) 이라고 한다. 클라이언트 측에서 아이디와 비밀번호를 전달하면서 "제가 이 사용자입니다. 저를 허용해주세요" 라고 요청을 하면 서버는 데이터베이스에 저장된 정보와 비교해서 검증을 하는 것이다. 예를들어 OTP(One Time Password, 일회용 비밀번호)를 입력하는 것도 인증이고 네이버앱이나 카카오톡앱을 사용해 2단계인증을 하는 것도 인증 과정이다. 

그렇다면 로그인 후에 API를 요청할 때에는 어떤 과정이 있을까?

이전에 HTTP에 대해서 알아야한다. HTTP는 Stateless하다. 상태가 없다는 것인데, 클라이언트와 서버가 서로의 상태를 저장하지 않고 독립적으로 운용된,다는 것이다. 상태를 저장하지 않는다는 말은 무엇일까? 식당을 생각해보자. 손님은 점원에게 원하는 메뉴를 말한다. 알러지가 있어 땅콩을 빼달라고 한다. 점원은 준비를 한 뒤 그 손님에게 음식을 내갈 것이다. 손님은 음식을 먹은 뒤 결제를 카드로 한다. 이것은 점원이 손님의 상태를 기억한 것이다. 땅콩을 빼달라고 했던 손님을 기억하고, 몇 번 테이블에 앉았는지 기억하고, 어떤 메뉴를 시켰었는지 기억해 결제를 해주는 것이다. 그러나 Stateless라면 어떨까? 점원은 손님이 요청을 할 때마다 그 전 상황을 까먹고 네? 뭐유...? 이럴 것이다. 

이런 상태에서 요청이 들어오면 서버는 맨날 로그인을 요구할 것이다. 그렇지만 어떤 요청을 할 때마다 접근이 허용된 사용자를 검증하자고 로그인 과정(인증)을 거칠 수는 없는 일이다. 이럴 때엔 인가(Authorization) 라는 방법을 쓴다. 요청마다 접근을 할 수 있는 권한이 있는지 요청을 체크를 한 뒤에 처리하는 방법이다. 예를 들어서 user1과 user2가 있을 때 user1은 자신이 쓴 글에 대해서 수정, 삭제가 가능하지만 user2는 user1이 쓴 글에 대해서 수정, 삭제 권한이 없다. 그리고 user1과 user2가 일반사용자라고 할 때에 관리자 페이지 등 관리자만 접근이 허용된 요청에 대해서는 접근을 할 수 없다. 

클라이언트측에서 브라우저에 인증이 성공한 아이디와 패스워드를 저장해두면 어떨까? 이 방법은 보안에 취약하다. 따라서 HTTP 환경에서 웹 어플리케이션은 "쿠키"나 "세션" 또는 "토큰"이란 방법을 사용해 서버의 인가를 받는다. 

쿠키란?

1. 클라이언트(사용자)는 로그인(인증)을 한다. 

2. 서버에서는 응답(response) 헤더에 Set-Cookie 라는 key로 쿠키를 보낸다. 클라이언트는 Set-Cookie 값을 브라우저에 저장한다.

3. 클라이언트는 필요에 의해서 요청을 보낸다. 예를들어 GET /user/info 요청을 보낸다고 한다. 클라이언트는 이 요청(request) 헤더에 cookie 정보를 넣어서 요청을 보낸다.

4. 서버는 요청 헤더의 cookie 정보를 분석해 이 요청이 접근이 허용된(인가된) 클라이언트에게 온 것인지 확인하고 요청한 데이터를 담아 클라이언트에게 응답(response)한다.

 

이 방법은 언뜻 보기에 합리적여보이지만 단점이 있다. 쿠키에 정보가 많으면 매 요청마다 큰 오버헤드가 발생한다. 일반적인 브라우저에서는 이 오버헤드를 제한하기 위해서 쿠키의 데이터를 4kb로 용량을 제한하고, 사이트당 쿠키 갯수도 20개로 제한한다. 또한 쿠키는 만료시간을 명시하지 않으면 브라우저 종료시 휘발되지만, 이 쿠키의 만료시간을 명시하면 클라이언트 환경에 파일로 저장이 되어서 브라우저가 종료가 되어도 휘발되지는 않지만 민감한 정보를 가지고 있다고 했을 때 파일의 형태로 있다면 해커에게 탈취되기가 쉽다.

 

세션

이런 쿠키의 단점을 해결하기 위해서 인가 정보를 클라이언트가 아닌 서버에 저장할 수 있는 세션 방법이 고안된다.

 

1. 클라이언트는 로그인을 하고, 서버는 세션아이디를 생성해 데이터베이스에 저장 한 뒤  응답헤더에도 Set-cookie를 키로 보낸다.

2. 클라이언트는 다음 요청시 세션아이디를 요청 헤더에 넣어 요청한다.

3. 서버는 요청에서 세션아이디를 꺼내 데이터베이스에 저장된 세션아이디와 내용이 같은지, 기간은 유효한지, 어떤사용자인지 식별을 한뒤 인가된 사용자이면 요청받은 데이터를 담아 응답을 한다.

 

이 방법은 예전에 했던 프로젝트에서도 사용된 방법이고, 현재에도 많이 사용되고는 한다. 하지만 로드 밸런싱 등의 이유로 서버를 수평확장(Scale Out) 하는 경우에 세션 방식의 인가는 비용이 많이 발생한다. 수평확장이 뭘까?

서버의 증설 방식에는 수직확장과 수평확장이 있는데, 수직확장은 단일 서버의 사양을 높여서 (램의 크기를 늘린다거나, cpu를 교체한다거나..) 서버를 업그레이드 하는 방식이다. 이 방법은 서버 업그레이드시에 어쩔 수 없이 서버를 다운해야하는 경우가 생기고 스펙이 높아질 수록 높은 비용을 지불해야한다. 또한 시스템에서 문제가 생겨버리면 모든 시스템을 사용할 수 없어 장애가 났을 때 치명적이고 이벤트 페이지같은 특별한 이슈때문에 수직 확장을 하는 것은 오버스펙으로 인한 비용의 증가만 초래한다. 그래서 요새는 이러한 단점을 보완하기 위해 수평확장을 하는데, 이것은 서버 증설이 필요할 때 서버를 새로 추가 설치하는 방법이다. 수직확장에 비해서 관리도 어렵고 기술적으로 하기도 어렵다는 단점이 있고, 네트워크를 통해 연결되어서 네트워크 상황에 따라 성능이 달라진다는 단점이 있다. 그러나 장애 발생시에도 서비스를 이용할 수 있어서 장애에 치명적이지 않고, 필요에 따라 확장했다가 줄이기가 용이하다는 장점이 있어 일반적으로 더 선호하는 방식이 되었다.

이런 환경의 변화로 인해서 세션 데이터에 대한 추가적인 작업이 필요해졌다. 만약 A서버와 B서버가 있을 때를 생각해보자. 이 서버들에서는 메모리에서 세션 정보를 관리한다. A서버에서 인증이 이뤄져서 세션 정보가 A서버의 메모리에 저장되었다. 그런데 다음 요청시 A서버가 바빠서 B서버로 요청이 들어가면, B서버의 메모리에는 해당 클라이언트에 대한 정보가 없기에 접근이 거절된다. 세션이 불일치하는 것이다. 이런 문제를 해결하기 위해서 다양한 해결책이 제시되었는데 첫째로 유저의 요청이 무조건 세션을 생성한 서버로 향하도록 하는 Sticky Session이 있다. 두번째로는 여러 웹서버가 동일한 세션 정보를 가질 수 있게 하는 Session Clustering이 있다. 마지막으로는 세션 정보를 관리하는 서버를 아예 별개로 빼둔 Session Storage이다. 마지막 방법이 많이 사용되곤 한다.

그런데 이렇게 되면 Session Storage를 관리하는 비용이 추가로 든다. 그래서 또다른 방식이 제안된다. 바로 토큰이다.

 

토큰 

 

1. 클라이언트에서 로그인을 하면 서버에서는 암호화된 토큰을 담은 응답을 보낸다.

2. 클라이언트는 새로운 요청때 요청 헤더에 이 토큰을 담는다.

3. 서버에서는 이 토큰을 검증한다. 

 

인증 정보를 클라이언트가 직접 들고 있는 방식이다. 이번 프로젝트에는 토큰 방식으로 유명한 JWT(Json Web Token) 라이브러리를 사용해보았다. 이 방식이 앞서 설명한 쿠키와 다른점이 바로 "디지털 서명"을 한다는 것이다. 서버에서 싸인을 하고, 클라이언트가 다음 요청때 보낸 토큰을 보고 이게 내가 싸인해서 보낸 토큰인지 검증하는 과정을 거친다. 세션을 서버에서 관리하지 않는 대신 보안을 좀더 높였다고 보면된다.

그렇다면 토큰 방식의 인가가 모든 상황의 정답일까? 그것은 아니다. JWT를 보면 JWT는 사용자의 인증정보, 토큰 발급시각, 만료시간, 토큰ID 등 담고 있는 정보가 세션ID보다 커서 더 많은 네트워크 트래픽을 사용하게 된다. 또한 토큰에는 payload 라고 암호화되지 않은 정보를 담는데 보안때문에 민감한 데이터를 실을 수는 없다. 또, 세션의 경우 인증정보를 서버에서 관리하기 때문에 해커에 의해 털렸다 싶으면 서버측에서 세션 정보를 지워버려서 무효처리를 하면되는데 토큰을 그럴 수가 없다. 요새 토큰방식으로 인가를 하는 사이트가 많아진 것은 서버의 부담이 증가되는 단점과 앞서말한 서버 확장 방식 때문이다.

서버를 수평확장 방식으로 확장했을 때 세션 기반 방식이라면 세션 불일치 문제가 따라와 Sticky Session, Session Clustering, 세션 스토리지를 따로 관리하는 방식으로 해결을 한다고 했다. 이럴 때 서버가 하나 더 추가가되면 스티키와 클러스터링방식일 때 추가로 작업이 필요하다. 또 세션 서버를 따로 관리하더라도 세션 서버에 문제가 생기면 세션 서버를 사용하는 다른 서버들에 모두 장애가 생기고, 세션 정보를 클러스터링 할 따른 세션 서버를 만들고... 이렇게 추가적인 작업들이 생기게 되어 상대적으로 더 비용이 적은 토큰 방식의 인증을 사용하게 되는 것이다.

 

이런 장단점들을 정리하면서 내가 선택한 방식은 jwt를 이용한 토큰 방식이었다. 지금 개발하는 서비스의 경우 1) 개발 기간이 넉넉하지 않고, 2) 먼저 회사 내에서 사용을 할 것이고 (높은 서버 성능이 필요하지 않음), 3) 서비스를 이용할 고객 수를 아직은 가늠하기 어려운 문제. 가 있어 세션 방식을 사용하는 것은 현 시점에서 큰 비용이라고 판단이 됐기 때문이다. 보안적으로도 토큰이 탈취되었다고 판단이 되면 서버의 secret key를 변경하면 그만이라고 생각했다. 각각의 상황에 따라서 방식은 달라질 수 있으니 맞는 방법을 선택하자! 

 

 

반응형