개발자로 회사를 다니면서 서비스 개발은 많이 해왔는데
기회가 없어 로그인 쪽으로는 단 한 번도 작업해 본적이 없다.
이직을 위해 공부도 할겸 해서 바닥 부터 만들어 보자 이렇게 마음먹었고
로그인 부터 개발하고자 마음 먹었다.
인증을 위한 수단으로
HTTP 자체 제공 프레임워크 부터 세션, JWT 까지 모두 정의해 보았다.
음 좋아 정리 다했으니, 이제 로그인 작업을 해볼까? 근데 바로 벽에 부딪혔다.
근데 어느 걸 사용하는게 더 나은거지?
그래서 단점부터 상세하기 정리하기로 마음 먹었다.
(장점은 이미 위에서 정리했다.)
세션은 다음과 같이 생성되고 동작된다.
위 문장을 봤을 때 고려되는 점들이 있는데 주의해야할 특징을 보자면
서버에서 인메모리에 저장한다는 점,
위로 인해 야기 되는 문제점들을 생각해봤다.
인메모리에 저장된다는 것은 사용자 수가 증가하면 인메모리도 그만큼 부담이 증가한다. 이는 바로 서버의 부담으로 이어진다.
또한, 비즈니스 로직을 개발하면서 서버를 재배포한다면 메모리의 특성상 전부 날아가버린다.
이러한 이유로 세션 저장소로 인메모리를 택하는 것에는 한계가 있다.
(물론 인메모리 자체에서도 유지하는 방법은 있을 것이다.)
유저가 요청 시 마다, 쿠키에 포함 되어 전송한다는 점
인증 정보이기도 한 세션 정보가 쿠키에 담겨 있어, 보안에 취약하다.
쿠키에 httpOnly, Secure 등의 보안 옵션 작업이 필요하다.
프론트와 백엔드 서버가 구분되어 있다면 쿠키 사용에 따른 CORS 문제점을 회피할 수 없다.
요청을 받은 서버는 쿠키에 포함된, 세션 정보를 검증
위 문구 자체에서는 크게 문제점을 찾을 수 없으나, 서비스가 매우 커져서 사용자가 급증한다면 어떻게 될까? 우리는 서버를 늘려 트래픽을 받아야 할 것이다.
(스케일 업(scale-up)으로는 성능과 가격을 고민 해봤을 때 한계가 있으므로,
스케일 아웃(scale-out) 을 택하게 될 것이다.)
이때 문제가 발생하게 된다.
즉 서버가 여러 대인 환경에서
유저가 로그인을 통해 최초 요청받아 세션 정보를 저장하고 있는 서버와
이후 요청으로, 쿠키의 세션 정보를 저장하고 있는 서버가 다르므로 세션을 검증 할 수가 없다.
그럼 위와 같은 문제를 해결하려면 어떻게 해야 할까?
Sticky Session
특정 유저에 대한 세션 정보를 가지고 있는 서버로 요청을 라우팅 하는 것이다.
오~ 그럴싸하다. 하지만 문제가 생긴다.
이러한 이유로 Sticky Session 을 활용하는 방법은 지양되어진다.
Session Clustering
세션 클러스터링으로 세션을 논리적으로 묶는 것이다.
세션을 생성될 때마다 복제하여 각 서버의 세션 정보를 일치시켜 정합성 이슈를 해결하는 것을 말한다. 이것도 괜찮아 보인다. 하지만 역시 문제가 있다.
역시 쉽지 않다. 세션 클러스터링도 그럼 지양해야 겠다.
Session Storage
결국 돌고돌아 세션 저장소를 분리하기로 마음 먹는다.
각각의 서버에 세션 정보를 저장하는 것이 아니라 외부에 저장소를 만들고 이 서버에 모든 데이터를 저장하는 것이다.
세션 저장소는 트래픽의 규모와 서비스 형태에 따라 다양한 것(RDB, In-memory)을 택할 수 있다. (돈이 많다면 당연히 속도를 보장하는 In-memory Database를 추천한다. )
주의해야 할 점은 세션 저장소가 1대인 경우에도 SPOF 문제점을 지니고 있으므로, 데이터베이스 replication 이나 failover 기능을 적극 활용해야 한다.
JWT의 동작을 정리해보자.
여기서 주의해야 할 문구를 찾아보자.
유저가 토큰을 브라우저 저장소에 보관하고 요청을 보낸다.
여기서 말하는 브라우저 저장소는 어디일까?
F12를 눌러 콘솔창을 켜보자.
쿠키 외에도 여러 저장소가 보이는데
브라우저에서 쿠키를 사용하는 것보다 훨씬 직관적으로 key/value 데이터를 안전하게 저장할 수 있는 메커니즘을 제공한다.
Storage 객체는 모두 window 객체 안에 들어가 있으며, 종류로는 2가지가 있다.
로컬 스토리지(Local Storage)
세션 스토리지(Session Storage)
로컬 스토리지(Local Storage)
각 Origin 별로 스토리지를 관리한다, 브라우저가 닫히거나 다시 열리더라도 유지된다.
Origin이 같다면 데이터는 모든 탭과 창에서 공유된다.
유효 기간없이 데이터 저장하며, 자바스크립트 혹은 브라우저 캐시, 내부적으로 저장된 데이터 지움으로써만 데이터 제거가 가능하다.
세션 스토리지(Session Storage)
각 Origin 별로 스토리지를 관리하는데, 브라우저를 종료하는 경우 데이터가 자동으로 제거 된다. 같은 도메인이라도 세션이 다를 경우 접근할 수 없다, 이 말은 같은 페이지라도 다른 탭에 있으면 다른 곳에 저장되기 때문이다.
즉 말하자면, Origin 뿐만 아니라 브라우저 탭에도 종속되어 있다. 이러한 이유로 잘 사용하지 않는다.
페이지를 새로 고침하여도 데이터가 사라지지 않고 남아 있다.
설명을 써봤는데 매우 좋아 보인다. 그러면 이 웹스토리지의 특징을 고려하여 여기에 저장을 하면 될까?
꼭 그렇지는 않다. 트레이드 오프를 고려하여 선택해야 하는데
Web Storage는 XSS에 취약한 반면에 쿠키에는 httpOnly 설정이 가능해 XSS를 완전히 방지하는 것은 아니나 Web Storage에 비해 좀더 낫다.(완전히 방어하는 것은 X)
즉, XSS 공격에 대해서는 쿠키 승
그러면 쿠키를 택해야 할까? 바로 선택할 수 없다.
CSRF 에 대해서 생각해보면 Web Storage는 HTTP 헤더에 토큰을 포함시켜 보낼테고,
쿠키는 자동으로 포함이 되어 있을 것이므로 CSRF에 대한 방어 측면에서는 Web Storage가 낫다.
즉, CSRF 공격에 대해서는 WebStorage 승
그럼 무얼 택해야 할까?
개인적으로 쿠키가 좀더 나은거 같다.
프론트 개발자는 아니지만 결국 js 문제 때문에서라도 쿠키에 httpOnly, Secure 설정이 되어있을 뿐만 아니라 CORS에 대한 대비도 되어 있을 것이므로
매번 WebStorage에 접근하고 관리하는 것보다는 자동으로 관리되는 쿠키가 더 나아 보인다.
위와 같은 보안 문제만 해결한다면 (세션도 쿠키에 보관한다면 같은 문제가 존재하지만) JWT 훨씬 나은 것 같다.
JWT는 유저가 보관하니까 서버의 부담이 줄어 훨씬 나은 것일까?
사실 문맥만 봐서는 맞다고 생각한다.
관리의 책임이 서버에서 유저로 이동한 것이니까 다만 보안취약점이 더 높아졌다.
관리를 유저의 브라우저에서 하므로 유저가 어디에 접속할지 무슨 행동을 할지 전혀 예측할 수 없고, 이러한 문제로 JWT는 매우 쉽게 탈취당할 수 있다.
유효기간을 설정했다면 다행이지만.. 설정 안한 경우에는? 큰 사고로 이어질 수 있다.
유효기간을 짧게 했다고 하자. 그럼 사용자의 로그인 주기가 점점 잦아진다.
JWT Refresh Token
이를 위해 access token 외에도 refresh token 이라는 재발급에 관한 토큰을 동시에 발급한다.
형태 자체는 똑같은 JWT 지만 긴 유효기간을 가지고, Access token 이 만료되었을 때
서버로 같이 보내어진 Refresh Token을 데이터베이스에 있는 것과 비교해서 일치하면, 다시 access token을 발급하는 것이다.
관련해서 자료를 찾아보니 이런식으로 사용한다고 한다.
case1 : access token과 refresh token 모두가 만료된 경우 → 에러 발생 (재 로그인하여 둘다 새로 발급)
case2 : access token은 만료됐지만, refresh token은 유효한 경우 → refresh token을 검증하여 access token 재발급
case3 : access token은 유효하지만, refresh token은 만료된 경우 → access token을 검증하여 refresh token 재발급
case4 : access token과 refresh token 모두가 유효한 경우 → 정상 처리
refresh token을 검증하여 access token 재발급, 이라는 말은
클라이언트(쿠키, 웹스토리지)에 저장되어있는 refresh token과 서버 DB에 저장되어있는 refresh token 일치성을 확인한 뒤 access token 재발급한다.
access token을 검증하여 refresh token 재발급, 이라는 말은
access token이 유효하다라는 것은 이미 인증된 것과 마찬가지니 바로 refresh token 재발급한다.
그럼 결국 JWT 탈취 문제점 때문에 Refresh token을 사용하면 데이터베이스에 보관해야 된다니까 이는 서버의 부담이 줄은게 전혀아닌데?
아니.. 그래도 따지자면 줄긴 줄었다.
세션은 매번 데이터베이스를 조회해야 하지만
JWT는 그나마 토큰이 둘다 모두 유효한다면 데이터베이스를 조회할 필요가 없다.
그래도 결국 트래픽이 증대한다면 세션과 비슷한 문제를 겪을 것임에는 틀림이 없다.
이외에도 토큰 무효화하거나 토큰을 탈취당했는데 탈취자가 먼저 토큰을 갱신한다면 어떻게 처리해야할까? 와 같은 무효화에 대한 추가적인 고민이 필요하다.
이러한 질문은 나만 떠오른 질문은 아닌가보다.
그래도 나는 JWT 를 택했다.
이유는 JWT의 장점에 있다.
JWT 가 무조건 나은 것은 아니다.
여러 해외 글을 찾아보니 이건
어떤 사과가 더 맛있나요? 가 아니라
사과 vs 오렌지를 비교하는 문제라고 한다.
즉 더 나은 것은 없다. 각자 장점도 있고 단점도 있다.
자기가 개발하려는 서비스의 환경에 맞춰 적절하게 선택해야 한다.
참조한 책 및 사이트
https://velog.io/@0307kwon/JWT%EB%8A%94-%EC%96%B4%EB%94%94%EC%97%90-%EC%A0%80%EC%9E%A5%ED%95%B4%EC%95%BC%ED%95%A0%EA%B9%8C-localStorage-vs-cookie
https://creeraria.tistory.com/38
https://kchanguk.tistory.com/146
https://hshine1226.medium.com/localstorage-vs-cookies-jwt-%ED%86%A0%ED%81%B0%EC%9D%84-%EC%95%88%EC%A0%84%ED%95%98%EA%B2%8C-%EC%A0%80%EC%9E%A5%ED%95%98%EA%B8%B0-%EC%9C%84%ED%95%B4-%EC%95%8C%EC%95%84%EC%95%BC%ED%95%A0-%EB%AA%A8%EB%93%A0%EA%B2%83-4fb7fb41327c
https://inpa.tistory.com/entry/WEB-%F0%9F%93%9A-Access-Token-Refresh-Token-%EC%9B%90%EB%A6%AC-feat-JWT#refresh_token%EC%9D%B4_%EC%99%9C_%ED%95%84%EC%9A%94%ED%95%9C%EA%B0%80