이전 포스팅에서 Spring Security에서 발생하는 필터 문제를 해결하던 중에 아래와 같은 문제를 추가적으로 확인하였다.
SecurityConfig
클래스에 "/products"
경로에 대한 접근을 인증하지 않도록 추가해주었다.
하지만 내 프로젝트에서는 상품을 등록할 때에는 반드시 로그인이 되어있는 상태에서 상품추가에 접근할 수 있도록 해야만한다.
따라서 상품등록 리소스인 GET - /products/new
에 비로그인 상태에서 접근이 불가능한지 확인할 필요가 있다.
상품을 등록하는 과정은 아래와 같다.
- 로그인
- 상품등록 폼으로 이동
- 상품등록 POST 요청을 통하여 서버에 상품을 등록
위 과정에서 만약 로그인이 되어있지 않은 상태라면 로그인창으로 리다이렉트해야한다.
현재 애플리케이션에서 확인해보니 비로그인상태에서 상품등록페이지로 이동은 가능하지만 모든 폼을 입력한후 등록하기 버튼을 클릭하니 403Forbbiden이 발생한다.
문제 개요
문제를 고민하다보니 아래와 같은 고민거리들이 생겨났다.
API 설계를 한다면 일반적으로 상품을 등록하는 과정을 아래와 같이 설계할 것이다.GET - /products/new
: 상품 등록페이지 조회POST - /products/new
: 상품 등록요청
하지만 필자는 리액트에 대한 지식이 얉으며 리액트를 시작하게 된 이유도 백엔드를 구현하던 중 "응답객체를 어떻게 반환해야 프론트입장에서 베스트 프랙티스를 취할 수 있는가?"라는 궁금증때문에 오로지 API통신만을 위한 리액트였다.
그렇기때문에 편리하게 구현하기위해 상품등록 페이지를 백엔드의 개입없이 클라이언트딴에서 Route
를 사용하여 폼양식만 제공하도록 설계하였다.
하지만 문제를 고민해보니 내가 설계한 방식과같이 리액트코드로 단순히 상품등록 폼을 제공할 경우 백엔드와 통신을 하지 않게되고, Spring Security와 통합할 방법이 없기때문에 인증절차를 수행할 수 없게된다.
그럼 처음에 설명했던 것처럼 백엔드에 GET요청을 통해서 상품등록 페이지를 불러와야하는가?
아니면 프론트에서 따로 처리할방법이 있는지에 대해서 고민하게 되었다.
문제 원인 찾고 해결해보기
검색을 해보니 프론트에서 해결할 방법도 있고 백엔드에서 해결하는 방법 모두 있었으며,
각각의 방법에는 장단점이 있었다.
먼저 각각의 방법에 대해 장단점을 알아보자.
프론트에서 상품등록 페이지를 제공하는 경우
- 장점
- 클라이언트측에서 페이지가 즉시 렌더링되기때문에 즉각적인 반응이 가능하며, 추가적인 네트워크 요청이 필요없다.
- RESTful원칙을 준수한다.
- 백엔드는 오직 데이터를 처리하고, 프론트는 UI를 담당하기때문에 역할이 명확하게 분리된다.
- 단점
- 상품등록 페이지에서 사용자의 정보를 불러와야한다던가 서버의 데이터를 가져올수는 없다. (Fetch와 useEffect를 사용하면 해결은 할 수 있을 것 같음)
백엔드를 거쳐서 상품등록 폼을 제공하는 방식
- 장점
- 상품 카테고리등과 같이 서버의 데이터를 포함하여 폼을 제공할 수 있다.
- 접근권한을 Spring Security와 연동하여 제어할 수 있다.
- 단점
- 네트워크 요청이 추가적으로 필요하다
- SPA의 장점을 활용할 수 없게되며 Security로직 뿐만아니라 서버와 클라이언트간의 분리 또한 애매해지게된다.
각각의 방법에 대해 장단점을 따져본 결과 프론트에서 상품등록 페이지를 제공하는 방법이
백엔드와 프론트엔드의 역할분담이 명확해진다는 점이 가장큰 장점이라고 생각되었으며,
현재까지의 나의 요구사항으로는 상품등록 페이지에서는 서버의 데이터가 따로 필요하지 않기때문에 굳이 백엔드로부터 상품페이지를 받아올 필요는 없다고 판단하였다.
만약 혹시라도 필요하게 되더라도 useEffect
를 사용하여 백엔드와 통신하면 된다고 생각되었기때문에 프론트엔드에서 상품페이지를 등록하도록 결정하였다.
그렇다면 이제 그 다음 고민을 해보아야한다.
서버와의 통신없이 비로그인 상태에서 상품등록 페이지에 접근하는 것을 막아야하는데
어떻게 해결해볼까 고민하던 중에 떠오른 방법이 stateless한 설계를 했다는 것이다.
현재 나의 프로젝트 인증 방식은 로그인시에 JWT Token을 클라이언트에 전송하여 해당 토큰을 세션 토큰에 저장해놓도록 설계해놓았다.
따라서 로그인여부를 해당 토큰이 세션 스토리지에 존재하느냐, 존재하지 않느냐로 판단할 수 있다는 것이다.
만약 존재한다면 그대로 상품등록 페이지를 라우팅하면되고, 존재하지 않다면 로그인 페이지로 리다이렉트 시키면 될 거라고 생각했다.
코드 구현
토큰 유무를 확인후 리다이렉트를 위한 PrivateRoute
구현
import {Navigate} from "react-router-dom";
const isAuthenticated = () => {
const token = sessionStorage.getItem('token');
return token !== null;
};
export default function PrivateRoute({element: Component}) {
return (
isAuthenticated() ? Component : <Navigate to="/login" />
);
};
PrivateRoute
를 App
컴포넌트에 적용
<Route path="/newProduct" element={<PrivateRoute element={<ProductForm/>}/>} />
(핵심코드만 표기)
이제 백엔드의 도움없이 비로그인 유저를 식별하여 로그인되지 않은 상태일 경우 로그인 페이지로 리다이렉트 하는 구현을 성공적으로 완료하였다.
여기서 또 하나 이어지는 궁금증이 생기는데,
사용자가 만약 상품을 등록하던 과정중에, 페이지를 새로고침하거나 브라우저를 종료하게 된다면 어떻게 될까?
현재는 프론트에서 리액트 라우터를 통하여 상품등록 폼만 제공해주기때문에 모든 내용들이 날아가게 될 것이다.
만약에 우리가 상품등록페이지를 서버에 GET으로 요청하였다면 어찌저찌 데이터를 다시 불러올 수 있을 것 같은데 프론트 엔드에서 상품페이지를 제공하는 것이 효율적이라 판단하였기때문에 프론트안에서 해결해야한다. 어떤 방법이 있을지 다음 포스팅에서 알아보자
'디버깅 & 리팩터링' 카테고리의 다른 글
리소스 접근권한과 범위 설정 (0) | 2024.10.01 |
---|
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!