상황
현재 스낵게임에서는 사용자 인증에 JWT를 사용하고 있다.
그 이유는 다음과 같다.
- 배포의 용이성
- 분리 및 다중화의 용이성
배포 시 서버가 재시작되므로, 사용자는 매번 다시 로그인해야 한다.
인-메모리 세션 저장소는 어플리케이션을 끌 때 휘발되기 대문이다.
이런 방식은 수시로 배포가 일어나는 상황에서 불편을 초래한다.
하지만 해결을 위해 DB를 세션 저장소로 사용하기에는 너무 빈번한 쿼리가 비효율적이라고 생각한다.
(아주 바쁜 서버가 아니라면, 실제로는 큰 상관은 없을 것이다)
서버 모듈을 분리하거나, 다중화하기 용이하다.
토큰 자체가 상태를 가지기 때문에, 별도의 동기화 비용 없이 사용자 인증을 공유할 수 있다.
문제
지금까지는 토큰을 어떻게 보관해도 안전하다고 착각했다. 중요한 정보가 포함되지 않았기 때문이다.
그래서 클라이언트가 토큰을 LocalStorage에 손수 저장하고 불러오는 방식을 취했다.
이 방식은 명시적으로 저장하고, 원할 때 쉽게 참조할 수 있어 복잡성을 줄이기에 좋은 방법이라고 생각했다.
그러나 쿠키를 학습하던 중 문득 ‘지금 방식이 과연 CSRF, XSS에 안전할까?’ 라는 의문이 들었다.
브라우저 저장소
Local Storage
용도: 데이터를 브라우저에 영구적으로 저장하며, 사용자가 브라우저를 닫아도 데이터가 유지.
용량 제한: 대부분의 브라우저에서 5MB 내외의 데이터를 저장이 가능.
보안: XSS
공격에 취약할 수 있으며, 스크립트를 통해 접근할 수 있다.
Session Storage
용도: 브라우저 탭이나 윈도우가 열려 있는 동안에만 데이터를 저장. 탭/윈도우를 닫으면 데이터가 사라진다.
용량 제한: localStorage
와 유사하게 대략 5MB의 데이터 저장이 가능.
보안: localStorage
와 마찬가지로 XSS
공격에 취약.
Cookie
용도: 데이터를 사용자의 컴퓨터에 저장하며, HTTP 요청 시 서버로 전송. 사용자 인증, 세션 관리, 사용자 선호도 저장 등에 주로 사용됨.
용량 제한: 매우 작으며, 대략 4KB 정도의 데이터만 저장할 수 있음.
보안: HttpOnly
플래그를 설정하면 JavaScript를 통한 접근을 방지할 수 있으나(XSS
방지), CSRF
공격에 취약할 수 있다. 보안을 위해 Secure
, SameSite
플래그를 설정해 보안을 강화할 수 있음.
토큰을 어떤 저장소에 저장해야 안전할까?
- 위에서 조사한대로 토큰은 추가적인 보안조치가 가능하고
XSS
공격을 방지할 수 있는 쿠키가 저장하기에 적합한 저장소라고 생각이된다. HttpOnly
옵션으로 인해 클라이언트에서 토큰에 접근할 수 없어지지만 Snack Game은 클라이언트에서 따로 토큰을 다루지 않게 설계가 되어있으므로 큰 상관이 없다고 생각한다.
리프레쉬토큰의 탈취는 어떻게 알 수 있을까?
리프레쉬토큰이 탈취되면 공격자는 계속해서 새로운 액세스 토큰을 발급받을 수 있고 공격자가 계속해서 사용자의 권한으로 행동할 수 있다는점이다.
토큰기반 인증의 장점은 stateless
하다는 점이고 이에 따른 단점은 실제 피해가 발생하기 전까지 탈취 여부를 알 수 없다는 점이다. 그렇다면 리프레쉬토큰의 탈취를 어떻게 알 수 있을까?
Refresh Token Rotation(RTR)
리프레쉬토큰의 탈취 여부를 알 수 있는 방법으로 제시된 방법으로 새로운 액세스토큰을 요청할 때 새로운 리프레쉬토큰도 만들어 넘겨주는 방식이다.
리프레쉬토큰이 1회용이 되니 재사용 된다면 리프레쉬토큰의 유출을 감지할 수 있다.
클라이언트에서 저장되는 토큰의 암호화가 필요할까?
사용자의 비밀번호를 안전하게 저장하기 위해 솔팅salting
처리를 하는것 처럼 프론트엔드에서 토큰에 대한 암호화를 진행하는것이 좋을까?
리프레시 토큰은 길고 완전히 무작위로 생성된 문자열로 설계되어, 본질적으로 높은 무질서성을 가지고 레인보우 테이블과 같은 예측이나 사전 계산 공격에 매우 강한 저항력을 가진다.
높은 무질서성과 고유성을 갖는 리프레시 토큰은 비밀번호와 같은 방식으로 솔팅의 이점을 얻지 못한다. 솔팅은 주로 낮은 무질서성과 재사용 문제에 대한 대책으로 사용되는데, 적절하게 생성된 리프레시 토큰은 이러한 문제를 가지지 않는다.
리프레시 토큰의 보안성은 예측 불가능하고 복제할 수 없는 무작위성과 실현 불가능성에 있고 비밀번호의 보안 방법과는 성격이 달라 암호화가 필요 없다는 결론.
Snack Game은 어디까지 보안해야할까?
리프레쉬토큰을 쿠키에 담고 js에서 접근할 수 없도록 해도 쿠키를 탈취해 공격자가 먼저 액세스토큰을 요청하면 아주 골치아파진다. 그치만 Snack Game에서 여기까지 고민할 필요가 있을까?
조사를 하다보면 보안 관점에서 볼 때 프런트엔드 웹 애플리케이션에서 토큰을 보호하는 것은 사실상 불가능 이라고 말하고 있고 모든 공격을 완벽하게 막을수는 없으니 프로덕트의 성격에 따라 적절한 보안 기법을 선택해서 사용하는것이 좋은것 같다.
민감하지 않은 어플리케이션에서는 XSS공격의 영향이 적으니 RTR이 적절한 해결책이라고 한다. (민감한 어플리케이션의 경우 BFF(Backend for Frontend)패턴을 사용할 수 있다고 한다.)
우리 Snack Game에서 생각할 수 있는 문제는 아무래도 공격자가 임의로 게임 점수를 올리는것인데 우리는 게임 진행사항을 기록하고 채점을 진행하는 보안조치를 이미 하고있기 때문에 RTR로 충분하지 않을까 생각한다.
References
- LocalStorage 저장을 고민한 글들
- 모던 브라우저의 Cookie 전달 정책
- FE ↔ BE 서로 도메인이 다를 때, 쿠키 전달 방법
- refresh token의 보안과 관련한 글
- RTR과 관련한 글