HOC를 활용한 사용자 인가 처리 왜 궁금했을까❓
아카이뷰 서비스는 사용자 등급이 존재하여 접근 권한이 존재하여 일부 페이지에 접근이 가능하다. API 서버에서 Spring Security를 활용하여 사용자 권한을 체크하고 있지만 프론트엔드에서도 페이지 자체에 접근을 못하게 막아야 한다. 이를 해결하고 React의 HOC를 활용하여 사용자 접근을 관리해보려고 한다.
1. HOC(Higher Order Component)
고차 컴포넌트 – React
A JavaScript library for building user interfaces
ko.legacy.reactjs.org
HOC(Higher Order Component)의 약자로 컴포넌트 로직을 재사용하기 위한 React의 고급 기술이라고 설명한다. 컴포넌트를 가져와서 새로운 컴포넌트를 반환하는 것이라고 할 수 있다.
1.1. HOC 권한 검사 Flow
사용자가 접근하고자 하는 페이지 URL로 라우팅 요청
라우터는 HOC 컴포넌트와 접근 페이지를 함께 반환
먼저, HOC 컴포넌트가 실행되며 Redux에서 사용자 권한을 조회한 뒤 검사
만약, 해당 페이지에 접근 권한이 존재한다면 반환
권한이 존재하지 않다면 홈페이지로 라우팅
2. React 구현
2.1. USER
import React from "react";
import { Navigate } from "react-router-dom";
import { useSelector } from "react-redux";
const UserAuth = ({ children }) => {
const { isLoggedIn } = useSelector((state) => state.user);
return !isLoggedIn ? <Navigate to="/" /> : children;
};
export default UserAuth;
유저의 경우 가장 기본 등급으로 로그인 여부만 확인
2.2. MEMBER
import React from "react";
import { Navigate, useLocation } from "react-router-dom";
import { useSelector } from "react-redux";
const MemberAuth = ({ children }) => {
const { role, userId } = useSelector((state) => state.user);
const { state } = useLocation();
// URL 직접 접근일 경우
if (state == null) {
return <Navigate to="/" />;
}
// 본인의 글일 경우
if (userId === state.postId) {
return children;
}
// 본인의 글이 아닐 경우
return role === "ROLE_USER" || role === "ROLE_BLOCK" || role === "" ? (
<Navigate to="/search" />
) : (
children
);
};
export default MemberAuth;
URL로 직접 입력하여 접근할 경우 state 정보가 존재하지 않아 홈으로 라우팅
본인이 작성한 게시글일 경우 페이지 반환
본인이 작성하지 않은 글을 조회할 때, USER, BLOCK, 비로그인 상태이면 검색 페이지로 라우팅
사용자 등급이 MEMBER, ADMIN일 경우 페이지 반환
2.3. ADMIN
import React from "react";
import { Navigate } from "react-router-dom";
import { useSelector } from "react-redux";
const AdminAuth = ({ children }) => {
const { role } = useSelector((state) => state.user);
return role !== "ROLE_ADMIN" ? <Navigate to="/" /> : children;
};
export default AdminAuth;
2.4. App.jsx
function App() {
...
return (
<>
...
<div className="App">
<Routes>
<Route path="/" element={<HOM_P_01 />}></Route>
<Route path="/cal" element={<CAL_P_01 />}></Route>
<Route path="/myinterview" element={<UserAuth><MYI_P_01 /></UserAuth>}></Route>
<Route path="/addquestion" element={<MYI_P_02 />}></Route>
<Route path="/interview/detail" element={ <MemberAuth><MYI_P_02 /></MemberAuth>}></Route>
<Route path="/revise" element={<UserAuth><MYI_P_02_Modify /></UserAuth>}></Route>
<Route path="/mypage" element={<UserAuth><MYP_P_01 /></UserAuth>}></Route>
<Route path="/modify" element={<UserAuth><MYP_P_02 /></UserAuth>}></Route>
<Route path="/search" element={<SCH_P_01 />}></Route>
<Route path="/admin" element={<AdminAuth><ADM_P_01 /></AdminAuth>}></Route>
</Routes>
</div>
<Footer />
</div>
</>
);
}
export default App;