쿠키와 세션에 대한 포스팅에 이어 토큰에 대해 이야기해보고,
이전에 진행했던 프로젝트에서 로그인 방식을 구현했을 때 생각했던 고민과
실제 현업에선 어떻게 이뤄지는지에 대해 포스팅해볼까합니다.
쿠키방식만 사용한다면 서버는 사용자관련 데이터를 쿠키에 담아 사용하게 되고,
세션방식을 사용하게 된다면 세션 또한 세션ID를 쿠키에 담아 여러 요청을 주고 받게 됩니다.
하지만 이 두 방식 모두 데이터가 쿠키에 담겨있게 되고 쿠키가 탈취됐을 경우 문제점이 발생하게 됩니다.
또한 세션은 서버가 데이터를 관리해야하기때문에 서버의 부담이 커지게 됩니다. 규모가 커짐에 따라 여러 서버를 사용하게 되는경우, 세션 데이터 공유를 위해 공통의 세션 스토어를 구축하거나, 세션 클러스트링을 통해 분산된 서버간의 세션 데이터를 공유해야 합니다.
따라서 개발자들은 서버의 부담을 줄이기 위해 서버에 인증상태를 저장하는 것이 아니라 클라이언트 쪽으로 저장하려는 방법을 고민하게 됩니다.
이에 대한 방안으로 등장한 것이 토큰입니다.
토큰
서버의 부담을 줄이고, 클라이언트 쪽에서 인증을 관리하는 식으로 고안해낸 것이 토큰입니다.
토큰은 통행권 같은 인증방식입니다. 토큰은 아래와 같은 단계로 발급됩니다.
- 유저가 로그인했을 경우 서버는 확인 후, 서버의 비밀키와 함께 암호화여 토큰 생성
- 토큰을 유저에게 `Authorization` 헤더에 전달
- 유저는 토큰을 브라우저의 세션 스토리지나 로컬 스토리지에 저장
- 유저는 매 요청 때마다 `Authorization` 헤더에 토큰을 같이 전달
- 서버는 유효한 토큰인지 확인
- 토큰이 유효하다면 요청에 대한 응답 데이터 전달
따라서 토큰은 서버가 아닌 클라이언트가 토큰을 통해 인증상태를 가지고 있게 됩니다.
또한 서버는 인증상태를 기억할 필요가 없기 때문에 서버는 무상태성
인 아키텍처를 구축할 수 있게 됩니다.
여러 서버에서 토큰의 유효성을 확인할 수 있고 여러 토큰을 발급 할 수 있습니다.
토큰 기반으로 인증을 구현할 때 대표적으로 JWT
방식이 있습니다.
JWT
는 JSON 객체에 정보를 담고 이를 토큰으로 암호화하여 전송하는 기술입니다.
유저가 서버에게 요청했을 때, 암호화된 토큰을 전달하고 서버는 토큰의 유효성을 검사해 인증정보를 확인합니다.
header/ payload / signature
aaaaaa.bbbbbbbbb.cccccccccc
JWT
는 위와 같은 구조로 되어있습니다.
- 헤더: 헤더에는 토큰에 대한 설명 (토큰의 종류, 사용된 알고리즘)
- 페이로드: 전달하려는 데이터 및 내용
- 시그니처: 암호화할 때 추가되는 비밀 키같은 역할
하지만 JWT
방식도 한계점이 있습니다.
토큰의 유효기간 설정을 짧게 한다면 인증을 자주해야하는 유저의 번거로움이 생깁니다.
그렇다고 유효기간을 길게 설정했을 시, 토큰이 탈취당하면 누군가가 사용자로 가장해 요청을 보낼 수 있습니다.
탈취를 당했더라도 강제적으로 서버가 토큰을 삭제할 수 없습니다.
페이로드에 데이터 양이 많아질 수록 토큰의 크기도 커지게 됩니다. 그렇다면 네트워크 전송 비용이 커지게 됩니다.
정리
- 장점
- 데이터 유지에 대한 서버의 부담이 사라진다.
- 인증상태를 기억할 필요가 없기 때문에 서버는 무상태성 아키텍처를 구축할 수 있다.
- 단점
- 토큰이 탈취당했을 경우, 유저로 가장하고 요청을 보낼 수 있다.
- 서버가 토큰을 삭제 할 수 없다.
- 토큰에 담는 데이터가 커지면 네트워크 전송 비용이 커진다.
AccessToken과 RefreshToken
따라서 이를 보완하기 위해 등장한 것이 AccessToken
과 RefreshToken
입니다.
앞서 토큰은 탈취 가능성이 있기 때문에 유효기간을 너무 길게 설정할 수 없었습니다.
따라서 유저가 자주 로그인해야하는 번거로움이 생기게 되었습니다.
AccessToken
과 RefreshToken
방식을 이를 해소할 수 있습니다.
AccessToken
과 RefreshToken
의 인증방식은 아래와 같습니다.
- 유저가 로그인한다.
- 서버는 인증정보를 검증하고 유효하다면 액세스 토큰과 리프레시 토큰을 발급합니다.
- 이때 유효기간은 액세스는 짧게, 리프레시는 길게 설정합니다.
- 유저는 전달받은 토큰들을 세션 스토리지나 로컬 스토리지에 저장합니다.
- 이후 매 요청 마다 **액세스 토큰**을 전달합니다.
- 서버는 유효한 토큰인지 확인하고 요청을 처리합니다.
하지만 만약에, 유효기간이 지난 액세스 토큰이라면 어떻게 할까요?
- 유저가 요청에 유효기간이 지난 액세스 토큰을 전달한다.
- 서버는 시간이 지나 유효하지 않은 토큰임에 대한 응답을 보낸다.
- 유저는 리프레시 토큰을 전달하며 새로운 액세스 토큰 발급을 요청한다.
- 서버는 리프레시 토큰의 유효성을 검사하고 액세스 토큰을 발급해 전달한다.
- 유저는 발급받은 토큰을 저장한다.
위의 같은 방식으로 서버는 토큰의 유효성을 검사하면서 인증방식을 구현하게 됩니다.
하지만 위 방식도 누군가 RefreshToken
을 갈취해 AccessToken
을 발급받을 수도 있는 한계점이 존재합니다.
정리
- 장점
- 보안을 위해
AccessToken
은 유효기간을 짧게 설정하면서 로그인을 유지할 수 있다.
- 보안을 위해
- 단점
- 제 3자가
RefreshToken
을 갈취해AccessToken
을 발급받을 수도 있다. AccessToken
,RefreshToken
모두 서버가 강제적으로 삭제할 수 없다.- Access Token이 만료될 때마다 새롭게 발급하는 과정에서 생기는 HTTP 요청 횟수가 많아지게 되고, 서버의 자원 낭비로 이어질 수 있다.
- 제 3자가
따라서 결과적으로 여러 방식들은 완전한 보안이란 존재치 않으며,
보안뿐만 아니라 보안과 사용자 경험 사이에 적절한 균형을 찾기위해 만들어진 것입니다.
개발자로서 내가 구현하려는 기능과 서비스에 따라 적절히 방법을 선택해 적용하는 것이 중요합니다.
적용방법
로그인을 오래 유지해야하고, 사용자가 선호하는 세팅방식을 오래 유지해야하는 OTT서비스 같은 경우에는 세션 방식을 사용하는 것이 좋습니다.
만약 사용자가 로그인되어있는 모든 기기를 삭제하고 싶다고 가정한다고 하면 서버가 세션을 삭제해버리면 됩니다.
이후 사용자가 재로그인 한다면, 유저가 가진 세션 ID는 유효하지 않기 때문에 다시 로그인하여 세션 ID를 발급받아야합니다.
반대로 로그인을 짧게 유지해도 상관이 없으며 페이로드에 가벼운 데이터들만 주고받아도 된다면 토큰 방식이 적합합니다.