팀 프로젝트 진행시 로그인 구현에대한 고민을 적은 글이다.
팀 프로젝트에서 회원가입 로그인 로그아웃 로직의 구현을 맡았다.
프로젝트 요구조건에 JWT를 통한 로그인 구현이 있었지만 다른 로그인 방식은 어떤게 있고 만일 더 좋은 방법이 있다면 다른 방식으로 로그인을 구현해 보고 싶었다.
그리고 JWT를 구현하던 중에 JWT 토큰의 저장을 클라이언트에서 하는데 이를 쿠키에서 할지 로컬 스토리지에서 할지에 대해서도 고민이 되었다.
쿠키는 일단 서버가 사용자의 브라우저에 저장하는 데이터를 의미한다.
서버에서 전달 받은 쿠키를 클라이언트에서 저장해놨다 서버로 보내는 요청마다 쿠키를 같이 보내 사용자임을 인증하는 방식으로 동작한다고 생각하면 된다.
하지만 이 방법은 브라우저내에 저장 되기 때문에 언제든 쿠키에 사용자가 접근할 수 있고 탈취 후 악용할 수도 있다.
서버에서 세션 ID 를 발급해서 서버도 자기 DB에 사용자의 세션 ID를 저장하고 이를 출입증 삼아 클라이언트도 요청을 보낼때마다 쿠키에 세션 ID를 넘겨주는 방법이다.
매번 클라이언트에서 사용자의 정보를 보내는거보다는 탈취의 위험이 적기는 하지만 중간에 하이재킹 공격을 통해 악성 클라이언트가 사용자인척하고 서버에 접근하는 문제도 있을 수 있다.
그리고 서버에 추가적으로 세션 ID를 보관해야 하므로 추가적인 저장 공간도 필요하고 서버 확장성도 낮다는 단점이 있다.
서버가 JWT 토큰을 만들어 이를 사용자에게 보내고 이를 사용자는 매번 요청마다 JWT 토큰을 보내 사용자임을 인증하는 방식이다.
JWT 토큰은 중간에 탈취해서 악의적으로 변경한다고 해도 서버에서 SECRET_KEY를 통해 복화화중 위조된 토큰이라고 인지해서 접근 자체를 막을 수 있다.
그리고 해당 토큰의 정보를 서버는 따로 저장 공간을 만들어 저장하지 않아도 돼서 리소스 측면에서도 효과적이다.
하지만 JWT 토큰의 길이가 길고 무겁기 때문에 요청이 많아진다면 서버 리소스의 낭비로 이어질 수 있다.
그리고 한 번 발급한 토큰은 만료 될때까지 서버에서 관리할 수 없기 때문에 탈취당하거나 하면 대처할 방법이 없다.
기존에 JWT 토큰의 유효기간을 짧게 설정해서 탈취의 위험을 최대한 줄이는 방법이다.
추가적으로 Refresh Token도 발급해 기존 JWT 토큰의 유효기간이 끝났을 때 새로운 JWT 토큰을 발급 받는 방식이다.
외부 서비스의 인증 및 권한 부여를 관리하는 범용적인 프로토콜 이라고 한다.
우리 프로젝트에서는 JWT를 사용하기로 했다. 쿠키만을 auth에 사용하는 경우에는 탈취후 악용하기에 너무 위험했다.
세션의 경우에는 서버에 세션 id를 저장하기 때문에 서버의 확장성 측면에서 좋지 않고 DB의 한정된 자원을 활용하기 위해서 추가적으로 세션 ID를 관리하는건 힘들다는 생각이 들었다.
반면에 JWT는 토큰의 탈취의 위험은 있지만 쿠키와 세션의 단점을 보완하는 방식이라는 생각이 들었고 탈취로인한 문제는 JWT 토큰의 만료시간을 짧게 지정하고 추가적으로 Refresh 토큰을 사용하면 보안 이슈에 어느정도 보완이 될꺼라고 생각했다.
로컬 스토리지, 세션 스토리지와 쿠키 두가지 방법이 있었다.
일단 로컬 스토리지는 CSRF 공격에는 안전하고 XSS 공격에는 취약하고 쿠키는 XSS 공격으로부터는 완전히 안전하지 않다는 단점이 있다. 그리고 CSRF 공격에 취약하다.
결론적으로는 localstorage 방법을 선택했다. 일단 쿠키는 XSS 공격으로부터 httpOnly 옵션을 사용해도 완벽히 막을 수 없고 서버분이 한분이라서 쿠키의 추가적인 설정을 요구하기에는 시간이 부족했다.
https://github.com/Miintoo/smart-farm
두 번째 팀 프로젝트에서도 마찬가지로 Auth부분 개발을 맡았다.
이번에는 저번 프로젝트에서 있던 보안 이슈에 대해서 완벽하게 이해하고 최대한 대응하려는 생각으로 Auth 전략을 짰다.
일단 저번과 같은 이유로 JWT 토큰을 이용하기로 했다. 그리고 AccessToken과 RefreshToken을 모두 활용하기로 했다. 그 이유는 AccessToken의 유효시간을 짧게 설정해 탈취당했을때 위험을 해결하고 RefreshToken을 이용해 만료됐을때 새로운 AccessToken을 안전하게 발급받도록 할 생각으로 사용하기로 결정했다.
토큰의 저장과 관리에 대한 문제가 있는데 AccessToken은 payload로 응답이 오도록 하고 프론트에서 일반 변수에 저장해서 관리하도록 했다. 이렇게 토큰을 관리하는 이유가 외부 도메인에서 해당 토큰을 이용한 CSRF공격을 방지하기 변수에서 관리하도록 했다.
그리고 RefreshToken은 쿠키로 관리하도록 결정했다. 그 이유는 일단 사용자가 새로고침을 하거나 페이지를 나갔다오게 되면 AccessToken이 만료가 되어 버리기 때문에 로그아웃이 되게 된다. 이를 위해서 Silent Refresh 요청으로 새로운 AccessToken을 서버로부터 받아와야 하는데 이 Refresh Token을 저장하기 위한 저장소로는 브라우저에서 나가도 유지가 되어야 하기 때문에 쿠키를 선택하게 됐다.
추가적으로 localStorage에 저장할 수도 있지만 마찬가지로 브라우저에 저장되는 정보는 XSS와 CSRF 공격에 취약하기 때문에 쿠키의 경우에는 httpOnly 옵션과 secure 옵션을 주면 이런 취약점에 어느정도 보안이 가능하기 때문에 localstorage가 아닌 쿠키를 선택해서 구현했다.
크롬 브라우저의 경우에는 동일한 도메인이 아니면 쿠키가 저장되지 않는다. 이를 해결하기 위해서는 SSL발급을 받아 쿠키에 secure 옵션을 줘야 서로 다른 도메인끼리 쿠키의 저장이 가능해진다.