HTTP는 상태를 가지지 않는다.
HTTP는 stateless 프로토콜로 상태를 유지하지 않는다. 덕분에 확장성이 높다는 장점도 있지만 로그인 같은 상태를 유지해야 하는 로직에서 단점을 보인다. 때문에 쿠키와 세션 그리고 토큰 등을 사용해 상태 유지 기능을 구현한다.
로그인 처리 - 쿠키
서버에서 로그인에 성공하면 HTTP 응답에 쿠키를 담아서 브라우저에 전달한다. 그러면 브라우저는 앞으로 해당 쿠키를 지속해서 보내준다.
쿠키 생성
위 예제에서는 예시로 쿠키에 memberId를 넘겨주었다. 최초 로그인 후 쿠키에 memberId를 담아 보내면 브라우저에서 이후 모든 요청에 받은 쿠키를 보낸다. 덕분에 서버에서는 로그인한 사용자라는 것을 인식하고 권한을 부여한다.
쿠키와 보안 문제
- 쿠키값은 임의로 변경할 수 있어서 쿠키를 변경하면 다른 사용자가 된다.
- 쿠키에 보관된 정보는 훔쳐갈 수 있기 때문에 민감한 정보를 담기 애매하다.
- 쿠키의 정보가 나의 로컬 PC가 털릴 수도 있고, 네트워크 전송 구 간에서 털릴 수도 있다.
- 해커가 쿠키를 한번 훔쳐가면 평생 사용할 수 있다.
대안
- 쿠키에 중요한 값을 노출하지 않고, 사용자 별로 예측 불가능한 임의의 토큰(랜덤 값)을 노출하고, 서버에서 토큰과 사용자 id를 매핑해서 인식한다. 그리고 서버에서 토큰을 관리한다.
- 토큰은 해커가 임의의 값을 넣어도 찾을 수 없도록 예상 불가능해야 한다.
- 해커가 토큰을 털어가도 시간이 지나면 사용할 수 없도록 서버에서 해당 토큰의 만료시간을 짧게(예: 30분) 유지한다. 또는 해킹이 의심되는 경우 서버에서 해당 토큰을 강제로 제거하면 된다
로그인 처리 - 세션
서버에 중요한 정보를 보관하고 연결을 유지하는 방법을 세션이라 한다. 사용자 관련 정보를 쿠키에 전달했던 이전 방식과 달리 세션 id라는 사용자 정보와 무관한 값을 클라이언트에게 보낸다.
- 세션 ID는 추정 불가능한 랜덤 값이어야 한다.
- 생성된 세션 ID와 세션에 보관할 값을 서버의 세션 저장소에 보관한다.
세션 id를 응답 쿠키로 전달
클라이언트와 서버는 결국 쿠키로 연결되어야 한다. mySessionId라는 이름으로 세션 id를 쿠키에 담아서 전달하면 클라이언트는 쿠키 저장소에 세션 id를 보관한다.
여기서 중요한 포인트는 회원과 관련된 정보는 전혀 클라이언트에 전달하지 않는다는 것이다. 오직 추정 불가능한 세션 ID만 쿠키를 통해 클라이언트에 전달한다.
서버는 클라이언트가 전달한 세션 id 정보로 세션 저장소에서 사용자 정보를 조회한다.
세션을 사용해서 서버에서 중요한 정보를 관리하게 되었다. 덕분에 다음과 같은 보안 문제들을 해결할 수 있다.
- 쿠키 값을 변조 가능 -> 예상 불가능한 복잡한 세션 Id를 사용한다.
- 쿠키에 보관하는 정보는 클라이언트 해킹시 털릴 가능성이 있다. -> 세션 Id가 털려도 여기에는 중요한 정보가 없다.
- 쿠키 탈취 후 사용 해커가 토큰을 털어가도 시간이 지나면 사용할 수 없도록 서버에서 세션의 만료 시간을 짧게(예: 30분) 유지한다. 또는 해킹이 의심되는 경우 서버에서 해당 세션을 강제로 제거하면 된다.
세션의 단점
세션 저장소를 서버에 둬야 하기 때문에 로그인 사용자가 많아지면 서버에 부하가 발생할 수 있다. 그리고 규모가 큰 서비스에서 서버를 여러 개 운영하면 1번 서버에 저장된 세션을 3번 서버에서 조회해서 찾아야 하는 등의 세션 관리가 복잡해질 수도 있다.
로그인 처리 - JWT
서버 구성이 복잡하고 사용자가 많을 때 세션이라는 상태를 관리하는 데 어려움이 있었다. HTTP의 상태를 유지하지 않는 관점으로 돌아가 상태를 유지하지 않으면서 로그인을 유지할 수 있는 방식이 있는데 바로 토큰을 이용한 방식이다. 위키에서는 다음과 같이 설명한다.
JSON 웹 토큰은 선택적 서명 및 선택적 암호화를 사용하여 데이터를 만들기 위한 인터넷 표준으로, 페이로드는 몇몇 클레임(claim) 표명(assert)을 처리하는 JSON을 보관하고 있다.
- 사용자가 로그인을 하면 서버에서는 토큰을 클라이언트에게 발급한다. (이때 서버는 어떤 상태도 저장하지 않는다.)
- 서버에서 만든 토큰은 사용자의 어떤 정보를 비밀키로 암호화해서 만든 것으로 만료 기간이 정해져 있다.
- 클라이언트는 쿠키 처럼 이 토큰을 HTTP의 Authorization 헤더에 넣어서 보내 로그인된 사용자임을 알려준다.
- 서버에선 토큰의 만료 기간을 확인하고 비밀키로 복호화한 뒤 사용자를 식별한다.
서버는 만료 기간을 정한 토큰을 발급하고 상태를 저장하지 않음으로써 세션의 단점이었던 부하의 문제를 해결하고 암호화한 값을 보냄으로써 보안도 지킬 수 있다.
JWT의 단점
상태를 저장하지 않으니까 이미 발급해 버린 토큰을 제어할 수가 없다. 서버에서 로그아웃을 제어할 수 없으며 토큰이 만료되지 않은 기간 동안에는 토큰으로 접근하는 요청을 막을 방법이 없다.
예를 들어 한 기기에서만 로그인 가능한 서비스를 만들려는 경우를 생각해 보자. 스마트폰으로 로그인을 하고 있기에 토큰이 발급되었다. 이 상황에서 같은 계정으로 PC에서 로그인을 할 때 PC에서 토큰을 발급받고 스마트폰이 사용하고 있던 토큰은 무력화시켜줘야 하는데 무력화시킬 방법이 없는 것이다.
제 3자가 토큰을 탈취한 경우에도 막을 방법이 없다.
대안
- 로그인을 하면 서버는 토큰 2개를 발급한다.
- 하나는 access 토큰으로 만료 시간을 아주 짧게 설정하고, 하나는 refresh 토큰으로 만료 시간을 길게 잡는다.
- refresh 토큰은 DB 등에 저장해 놓는다.
- 클라이언트는 access 토큰으로 요청을 하다가 수명이 다하면 refresh 토큰을 보내 새로 access 토큰을 발급받는다.
- refresh 토큰만 안전하게 관리된다면 access 토큰이 만료되어도 사용자는 다시 로그인할 필요 없이 로그인을 유지할 수 있다.
- access 토큰이 탈취되어도 오래 쓰지 못하고, 로그아웃도 refresh 토큰을 삭제시킴으로써 구현할 수 있다.
토큰 하나만 쓸 때보단 괜찮지만 아주 확실한 해결 방법도 아니다. 결국 상태를 유지해야 하는 것이고 세션의 단점이 다시 부각되어진다고도 생각된다.
참고
'네트워크' 카테고리의 다른 글
HTTP 상태 코드 정리 (0) | 2022.05.22 |
---|---|
HTTP 특징 및 메서드 정리 (0) | 2022.05.07 |