문제

로그인 기능이 있는 프로젝트이다 보니 로그인한 사용자만 접근할 수 있도록 해야 하는 페이지가 존재한다.

간단한 게시판 프로젝트인 만큼 대부분의 권한이 필요한 페이지는 서버에 요청을 보내 데이터를 받는 경우가 대부분이기 때문에 접근에 대해 딱히 신경쓰지 않더라도 서버 요청에서 403이 발생해 접근할 수 없다.

데이터 역시 반환되지 않기 때문에 데이터가 출력될 걱정 역시 없고.

하지만 게시글 작성 페이지의 경우 문제가 달랐다.

아무런 데이터 요청이 필요하지 않았고 그러다보니 이걸 굳이 서버에 요청하게 두고 싶지 않았다.

 

글 작성 페이지는 애초에 서버에 get 요청을 보내지도 않기 때문에 아무런 정보가 존재하지 않고 페이지 내의 유일한 기능인 작성을 요청하기 위해서는 토큰을 같이 전송해야 한다.

토큰이 존재하지 않는데 이걸 어찌어찌 뚫고 들어온다고 하더라도 할 수 있는게 없다.

그럼 굳이 서버에 요청해 처리시간과 요청에 대한 비용을 발생시켜야 할까? 라는 생각이었다.

그래서 클라이언트에서 가볍게 검증할 수 있는 방법을 찾고자 했었다.

 

해결 방안으로 택했던 것은 Redux를 통한 로그인 상태 관리.

Navbar 컴포넌트는 처음 렌더링 될 때 서버에 사용자 로그인 여부를 요청하고 해당 응답에 따라 dispatch를 수행해 Redux에 상태 정보를 담을 수 있도록 처리했다.

 

게시글 작성 페이지에서는 이 Redux에 저장된 값을 useSelector로 가져와 로그인 상태가 아니라면 로그인 페이지로 이동하도록 처리했다.

 

처음에는 당연히 잘 되었으나, 해당 페이지에서 새로고침을 하는 경우 Redux에 있는 값이 초기화 되기 때문에 로그인한 사용자라도 로그인 페이지로 이동이 되었다.

또한 로그인하지 않은 사용자가 게시글 작성 페이지에 url 입력으로 접근하는 경우 페이지가 일단 렌더링 되었다가 로그인 페이지로 이동한다는 문제 역시 존재했다.

 

 

문제해결 과정

1. Redux persist로 해결하는 방안을 고려

가장 먼저 Redux를 통해 문제를 해결하고자 했다.

로그인 상태를 localStorage에 담고 Redux persist를 통해 렌더링 이전 스토리지에서 데이터를 확인하고 상태 값을 담은 뒤 렌더링 하도록 한다.

Redux persist를 사용해 localStorage에 담는 것은 이미 가장 처음에 Redux를 적용하면서 처리해본 방법이긴 했다.

현재는 persist를 통해 관리하지 않도록 수정한 상태인데 이유는 localStorage에 저장된 데이터가 얼마나 정확한지 파악할 수 없다는 것이 이유였다.

localStorage에 로그인이 된 상태라고 저장이 되어있더라도 토큰이 모두 만료되어 로그인 상태라고 판단할 수 없을 수 있다고 생각했다.

 

JWT를 공부하고 여러 케이스를 보면서 Authorization 토큰의 경우 localStorage 또는 쿠키에 담는 것이 보통이고 요청 이전 header에 담아 토큰을 체크한 뒤 사용자 요청을 보내거나 한번의 요청으로 토큰 체크까지 수행하는 경우가 많다.

이렇게 처리하는 경우 localStorage에 저장된 토큰이 유효한 토큰인지 요청마다 체크할 수 있게 되는데 그만큼 서버에 대한 요청 횟수가 많아지고 부담이 생길것이라고 생각했다.

아직은 제대로 학습하지 않았지만 MSA 처럼 여러개의 서버로 분리하는 아키텍쳐 구조라면 토큰만 검증하는 요청이 존재하고 사용자 요청은 다른 서버로 전달 될 것이기 때문에 부담이 줄어들겠지만 진행하고 있는 프로젝트처럼 단일 서버 구조에서라면 한번에 처리하는 방법도 나쁘지 않을 것이라고 판단해 설계했다.

그래서 모든 토큰을 쿠키에 저장하고 한번의 요청으로 처리하게 설계했는데, 단순하게 localStorage에 저장한 로그인 상태를 위해 서버에 요청하는 것은 AccessToken을 localStorage에 저장하는 것 보다 비효율적이라고 생각했다.

이렇게 처리되어야 한다면 생각되는 순서는 아래와 같았다.

 

localStorage에서 데이터 조회 -> 같이 저장한 데이터 저장일자 또는 만료일자 값을 통해 상태 체크 -> 만료 상태가 정상이라면 Redux에 저장, 비정상이라면 서버에 요청 후 localStorage에 재 저장한 뒤 Redux에 저장 -> 렌더링

 

'만료 상태가 정상이라면 서버에 요청할 필요가 없고 비정상인 경우에만 요청하면 된다. 그만큼 서버에 요청하는 횟수가 줄어들 것이다.' 라고 생각했다.

AccessToken의 경우 클라이언트에서 만료체크를 하는 것 보다는 서버에 보내 확실하게 체크하는 것이 더 낫다고 생각했기 때문에 Redux persist로 처리한다면 로그인 상태만 관리하도록 하거나 아니면 AccessToken 저장을 쿠키가 아닌 localStorage에 저장하도록 해 처리하도록 아예 수정하는 것이 낫겠다 싶었다.

그래서 메모만 해두고 일단 보류.

 

2. 비동기가 문제?

게시글 작성 페이지를 접근하면 아래와 같은 순서로 처리가 된다.

게시글 작성 페이지 접근 -> 렌더링 -> 선언된 useSelector 값을 useEffect에서 체크 -> useSelector 값에 따라 이후 처리

 

그리고 여기서 새로고침을 하면 아래와 같이 된다.

게시글 작성 페이지 접근 -> 렌더링 -> Navbar에서 useEffect를 통해 서버에 사용자 로그인 상태 요청 및 dispatch -> dispatch에 따른 작성 페이지 useEffect 수행 -> 이후 처리

 

이때 문제에 대해 알아보면서 알게 된 것이 useEffect가 렌더링 이후에 수행된다는 점이었다.

useEffect가 가장 먼저 수행이 될 것이라는 착각에서 발생한 문제였다.

하지만 useEffect를 통해 처리하지 않는다면 그건 또 그거대로 렌더링이 발생할때마다 해당 요청을 보내 처리한다는 문제가 있었다.

그래서 이 처리가 마무리될때까지 지연시킬 방법이 없을까 고민해봤다.

작성 페이지가 렌더링 되는 조건을 Navbar가 렌더링이 마무리 된 이후로 처리할 수 없을까? 라는 생각을 해봤는데

이건 방법을 찾을 수가 없었다..

 

그나마 처리할 수 있는 방법을 찾은게 persistGate를 통해 Redux가 처리되기 전까지 렌더링을 하지 않도록 막는 것이었는데 그렇게 하게 되면 localStorage에 사용자 상태를 담아야 했다.

좀 더 확실하게 처리할 수 있는 방법을 찾기 위해 1번 방법을 보류해두었는데 이건 사실상 그 방법에 대한 연장선이 되었다.

 

 

3. 페이지 접근 시 서버에 요청 전달

계속 이 방법은 피하고자 했으나 사실 가장 확실한 방법이긴 했다.

'별 기능이 없고 출력되는 데이터도 없다보니 서버에 굳이 요청을 안보내는 방법을 생각해보자' 라는 것에서 시작된 문제였지만 그래도 권한이 필요한 페이지이기 때문에 확인은 서버에 요청하는게 가장 베스트이긴 했다.

useEffect를 통해 해당 페이지 접근 시 서버에 요청을 보내고 그 결과에 따라 로그인 상태라면 setUserStatus로 state 값을 수정.

렌더링 조건으로 userStatus가 true 인 경우에만 수행하도록 처리해 처리되는 동안 먼저 렌더링되어 출력하는 것을 막을 수 있었다.

 

이 방법이 가장 확실하다고 생각해 수정하게 되었고 어제까지 이 상태로 올라가있었다.

 

4. 중복 요청

3번 처리 후 프로젝트에 대해 정리하면서 알게된 점이 있다.

작성 페이지 접근 후 새로고침을 하게 되면 로그인 상태 확인 요청이 두번 발생한다는 것이었다.

Navbar에서도 요청을 보내 dispatch를 수행하고 작성 페이지의 useEffect에서도 요청을 보낸다는 점.

수정 당시에는 생각도 못한 문제였다.

 

 

 

최종적인 해결방안

마지막 4번 문제에 대해서는 생각보다 간단하게 해결할 수 있었다.

처음에는 여러가지 방안을 고려해봤다.

insert 로 끝나는 url이라면 Navbar에서 요청을 수행하지 못하도록 막는다.

작성 컴포넌트에서 useSelector 값이 초기값인 default인 경우 아무것도 수행하지 않도록 하고 useEffect가 첫 렌더링시에만 수행하는 것이 아닌 매번 수행하도록 한다.

default 상태이더라도 useState 값을 건드려 계속해서 재렌더링 하도록 한다.

 

전부 다 비효율적이고 문제가 있는 방법들이었다.

첫번재 방법은 다른 페이지에서의 재 렌더링에서도 해당 조건문을 타기 때문에 불필요한 과정이 하나 늘어난다.

두번째 방법은 작성 페이지의 input 값에 대해 onChange로 state에 저장해 관리하는데 그럼 매번 useEffect가 수행이 된다.

세번째 방법은 만약 요청이 지연되서 useSelector 값이 들어오는데 시간이 걸린다면 default 인 경우 얼마나 값을 바꾸도록 수행할 것인가. 그리고 그로 인한 계속적인 재 렌더링을 어떻게 감당할 것인가.

 

그러다 정말 바보처럼 당연한 것에 대한 해답이 떠올랐다.

useSelector값의 변화에 따라 useEffect를 수행하게 하면 된다.

Redux에는 초기값으로 default가 설정되고 로그인한 상태라면 'loggedIn', 로그아웃 상태라면 'loggedOut' 값을 갖는다.

그럼 useEffect에서는 default인 경우 아무것도 처리하지 않고 useSelector 값이 변화될때마다 수행되도록 하면 이후 값에 대한 처리를 통해 해결할 수 있게 된다.

 

그럼 중복 문제도 해결할 수 있고 loggedIn 인 경우 userStatus를 true로 바꿔줄테니 setState에 의해 재 렌더링 되면서 조건을 만족해 제대로 렌더링 할 수 있다.

또한 userStatus가 true가 아니라면 컴포넌트를 제대로 출력하지 않기 때문에 불필요한 엘리먼트가 노출되는 것도 막을 수 있다.

 

 

정리

이걸 해결하는데 이렇게 오래 걸린 이유는...

가장 먼저 useEffect의 동작 원리에 대한 낮은 이해도, Redux 활용에 대한 문제가 가장 컸던 것 같다.

실제로 초기 로그인 상태값에 대한 Redux state 값은 boolean으로 처리했었다.

그래서 아직 요청이 확인되기 전에도 false 값을 가져 로그인하지 않은 사용자로 처리되곤 했었다.

이 문제가 발생하게 되면서 이것 저것 시도하면서 default, loggedIn, loggedOut으로 나눠 기본값을 default 로 가져가도록 수정하게 된 것인데 그렇게 수정한 것을 제대로 사용할 방법에 대해 좀 더 빨리 깨달았다면 금방 해결 될 문제였다.

 

+ Recent posts