프로젝트 시 처음으로 유저 인증 구현 부분을 맡았었는데, 초기에 리프레시 토큰을 새로운 액세스 토큰 자체로 오해하였다.
작성한 코드에서 액세스 토큰을 클라이언트에 노출해 보안 취약성이 발견되었다.
☑️ 해결방법
액세스 토큰 및 리프레시 토큰의 목적 및 용도를 먼저 이해하였다.
토큰 저장 방법과 전달 및 갱신 방법에 대한 탐색을 하였다.(유저 정보를 저장하는 방식? / 토큰을 어떻게 저장할 것인가? / 클라이언트에게 액세스 토큰을 직접 노출시키지 않고 안전하게 제공하는 방법이 무엇일까? / 액세스 토큰 만료 시 어떻게 새로운 액세스 토큰을 생성할 것인가? / 리프레시 토큰도 만료되면 어떻게 할 것인가?)
토큰 갱신 흐름을 설계하여 토큰 관리에 대한 체계를 구축하고, 코드로 작성하기 편하도록 하였다.
해결방법에 대한 자세한 내용은 아래에 정리되어 있다.
🍒 Access Token과 Refresh Token
Access Token의 목적과 한계
액세스 토큰이란 사용자가 리소스에 접근할 때 권한을 인증하기 위해 사용되는 토큰이다.
주로 웹 애플리케이션에서 사용자가 로그인하고 세션을 유지하는 동안에만 액세스 토큰이 유효하다.
목적
권한 부여 사용자가 인증된 상태에서 서비스나 리소스에 접근할 수 있는 권한을 부여한다.
보안 강화 서비스 접근 시 패스워드 등을 매번 요구하지 않고 토큰을 사용하여 인증을 간편하게 처리할 수 있다.
한계
짧은 수명 일반적으로 액세스 토큰은 보안을 위해서 짧은 유효기간으로 지정한다.(보통 1시간 내외) 유효기간이 짧은 이유는 만약 액세스 토큰이 탈취되었을 경우 해킹자가 유효 기간 동안 리소스에 자유롭게 접근할 수 있기 때문에 보안적인 문제를 방지하기 위함이다. 따라서 수명이 짧아 만료될 때마다 로그인을 하는 등 새로 인증을 해야하는 문제가 있다.
Refresh Token이 필요한 이유
리프레시 토큰이란 보통 액세스 토큰과 함께 발급되며, 액세스 토큰의 유효기간이 만료된 경우 갱신을 위한 목적으로 사용된다.
목적
액세스 토큰 갱신 액세스 토큰이 만료되었을 때, 리프레시 토큰을 사용하여 새로운 액세스 토큰을 발급받을 수 있다.
필요한 이유
장기간 인증 사용자는 액세스 토큰이 만료되었을 때 추가적인 로그인 과정 없이도 계속해서 서비스를 이용할 수 있다.
긴 수명 리프레시 토큰은 일반적으로 액세스 토큰보다 긴 유효기간을 가진다.(보통 2주) 사용자는 리프레시 토큰이 만료되었을 때 새로 인증하게 된다.
🍒 유저 정보 저장 방법
유저의 정보를 저장하는 방법은 아래와 같이 선택지가 많다. 완벽하게 안전한 저장 방법이란 없고 계속 고민해봐야할 부분이지만, 최대한 보안적으로 안전하다고 판단되는 방식을 선택하였다.
세션
유저 정보를 서버의 메모리에 저장한다. 보통 세션 ID를 쿠키로 전달받아 세션DB에서 해당 유저 정보를 조회하여 사용한다.
단점은 유저의 수가 증가하면 많은 유저의 세션 정보를 메모리에 저장해야 하므로, 서버 부하가 클 수 있다.
쿠키
서버에서 키와 값을 설정하여 브라우저에 저장하고, 해당 도메인에 요청을 보낼 때마다 헤더에 쿠키를 포함시킨다.
http-only 옵션을 설정해서 JS로부터 쿠키에 접근할 수 없도록 설정해주어야 XSS 공격으로부터 안전하다.
단점은 암호화가 되어있지 않고, 데이터 저장 용량이 적으며, 매번 요청 시마다 쿠키가 전송되어 트래픽이 증가할 수 있다.
Token
쿠키를 사용할 수 없는 환경에서 사용한다. 토큰을 사용해 세션DB에서 유저 정보를 찾는다.
단점은 토큰을 검증하고 나서 유저 정보를 찾는 과정이 추가되므로 서버 부하가 높아질 수 있고, 클라이언트에 저장되므로 XSS 공격에 취약할 수 있다.
Web storage
클라이언트에 데이터를 저장할 수 있게 HTML5부터 나온 새로운 방식의 데이터 저장소를 말한다. 키와 값의 형태로 데이터를 저장한다.
web storage 또한 클라이언트에 저장되므로 보안에 취약할 수 있다. 서버에 요청을 보내지 않으므로 서버 부하는 감소하지만, 클라이언트 측에서 데이터를 다룰 때 성능 저하가 발생될 수 있다.
로컬스토리지
클라이언트 기기에 데이터를 영구적으로 저장한다. 직접 삭제하지 않는 한 영구적으로 남아 있다.
세션스토리지
page session을 의미한다. 창이 닫히면 삭제되는 휘발성 데이터를 저장한다.
JWT
서버에 정보를 저장하지 않고도 로그인 상태를 유지할 수 있게 해준다.
사인 알고리즘을 사용하여 유저에게 사인을 보내고, 요청 시 사인을 확인하여 올바른 요청인지 확인한다.
단점은 요청이 많아질수록 서버 부하가 커질 수 있다.
이 중에서 내가 선택한 방법은 JWT 토큰에 정보를 저장하고, http-only 쿠키를 통해 클라이언트에 전달하는 방식을 선택했다. JWT의 편리함과 쿠키의 보안성을 결합하여, 사용자 정보를 안전하게 전달할 수 있기 때문이다. 일반적으로 JWT 토큰은 클라이언트에 저장되며, 클라이언트에서 토큰을 수정하거나 조작할 수 있는데 그 부분이 보안 취약점이 될 수 있다. 따라서 보안을 강화하기 위해 토큰을 클라이언트에 직접 저장하는 대신, http-only 옵션을 설정한 쿠키를 사용해서 토큰을 안전하게 저장하고 전달하였다.(secure, samesite 옵션도 고려해볼 수 있다)
🍒 토큰 갱신 흐름 정리
나는 다음과 같이 정리했다.
백엔드는 최대한 일어날 수 있는 다양한 경우의 수를 생각해야 한다고 느꼈다. 많은 유저가 접속했을 때나 지속적인 요청을 보냈을 때 서버에 부담은 없는지, 토큰을 탈취당했다면 서버에서 어떠한 문제를 일으킬 수 있는지 등에 대한 최악의 상황도 고려해서 그런 부분들을 끝없이 고민해봐야 한다고 생각했다.
또한 내가 선택한 방법도 완전히 안전한 방법은 아니기 때문에 어떻게 하면 좀 더 사용자 인증을 안전하게 처리할 수 있을지에 대한 고민도 끝없이 해야하는 것 같다.