시작하기 전에
부족한 부분은 언제든지 지적하십시오.
이 기사는 대답이 아니며 올바른 길을 보여주지 않습니다.
공부한 내용을 정리한 문장이므로 참고하십시오.
이 문서에서는 react-router를 사용하여 로그인 사용자의 권한에 따라 페이지 액세스 승인/거부 기능을 만듭니다.
+ 라우터 라이브러리는 React-router v6을 기반으로합니다.
v5와 약간 다르기 때문에 변경 사항은 공식 문서를 참조하십시오.
필요한 기능 정리
로그인한 경우와 로그인하지 않았을 때 액세스할 수 있는 페이지를 나누어
로그인한 경우 권한이 있는지 여부에 따라 액세스 가능한 페이지를 다시 분할하고 액세스할 수 없는 경우 기존 페이지로 이동하는 기능입니다.
위 내용을 정리하면 다음과 같습니다.
1. 로그인하지 않으면 액세스할 수 없는 페이지
2. 로그인하지 않으면 액세스하기 쉬운 페이지
2-2. 로그인해야 하며 + 권한이 있는 경우에만 액세스할 수 있는 페이지
3. 잘못된 액세스를 시도한 경우 액세스 가능한 페이지로 이동하거나 액세스할 수 없는 가이드 페이지를 표시합니다.
플로우차트를 작성하면 다음과 같이 됩니까? 아마 옳을 것입니다.
허;;
라우터 만들기
1. 로그인 토큰 관리
먼저 사이트에 액세스할 때 token(로그인했는지)이 있는지 확인합니다.
로그인은 토큰을 이용하여 관리하게 되었습니다.
sessionStorage와 localStorage의 두 가지 중에서 고민했지만 내가 만들고 싶은 기능에 필요한 만료일을 설정하기 위해 localStorage를 사용했습니다.
(쿠키를 사용한 로그인 관리는 제외되었습니다.
)
다음은 예입니다.
저장된 토큰은 다음과 같이 저장하고 필요에 따라 다른 정보를 넣으십시오.
expire가 만료 날짜를 설정한 값입니다.
// localStorage
token = {
"value": "token",
"userName": "kim",
"sort": "00",
"expire": 1676597289995
}
다음 makeLoginToken 함수는 로그인 시 서버에서 받은 정보로 토큰을 생성합니다.
이 시점에서 만료일을 설정하기 위해 tts을 설정합니다.
예제 코드에서는 하루를 ms로 단위를 변경한 시간 86400000으로 설정했습니다.
다른 시간이 필요한 경우 Google에서 단위 변경을 검색하면 쉽게 찾을 수 있습니다.
// makeLoginToken.tsx
interface makeLoginToken_TYPE {
tokenKeyName: string;
keyValue: string;
userName: string;
sort: "00" | "01";
}
export default function makeLoginToken({
tokenKeyName = "token",
keyValue,
userName,
sort,
}: makeLoginToken_TYPE) {
const tts = 86400000; // 1day/ms
const tokenValue = {
value: keyValue,
userName,
sort,
expire: Date.now() + tts, // 토큰 생산일 기준 + 1일 만료기간
};
const objectToString = JSON.stringify(tokenValue);
localStorage.setItem(tokenKeyName, objectToString);
}
첫 번째 사이트에 액세스하거나 페이지를 이동할 때마다 loginTokenExpiry 함수를 통해 토큰의 유무와 만료 날짜를 확인합니다.
1. 토큰이 존재하면서 유효한 경우 true를 반환합니다.
2. 토큰없거나 만료된 경우 토큰 삭제그리고 false를 반환합니다.
// loginTokenExpiry.ts
// 토큰 유무 확인 후 유효기간 체크
export default function loginTokenExpiry(tokenKeyName = "token") {
const localLoginToken = localStorage.getItem(tokenKeyName);
if (!
localLoginToken) return false;
const StringToObject = JSON.parse(localLoginToken);
if (Date.now() > StringToObject.expire) {
localStorage.removeItem(tokenKeyName);
return false;
} else return true;
}
2. LoginPrivateRoute
두 개의 PrivateRoute를 만듭니다.
먼저 로그인 페이지에 액세스할 때 작동하는 LoginPrivateRoute는 다음과 같이 실행됩니다.
1. 위의 loginTokenExpiry 함수를 실행하여 토큰 상태를 확인합니다.
2. loginTokenExpiry 반환 값을 기반으로 redirect() 실행 및
3. redirect() 를 실행하면 토큰의 sort 를 조회해, 「00」, 「01」의 경우는 해당의 url 에 이동, 양쪽 모두 「/permission」에 이동
4.
*<アウトレット/>무엇인지는 아래에서 설명합니다.
// LoginPrivateRoute.tsx
export function LoginPrivateRoute() {
const isAuthenticated = loginTokenExpiry();
return isAuthenticated ? redirect() : <Outlet />;
}
const redirect = () => {
const loginMember = JSON.parse(localStorage.getItem("token") as string);
if (loginMember.sort === "00") return <Navigate to="/management" />;
else if (loginMember.sort === "01") return <Navigate to="/user" />;
else return <Navigate to="/permission" />;
};
2-2 LoginPrivateRoute 사용 예
LoginPrivateRoute는 다음과 같이 element={ } 내에서 사용됩니다.
이렇게 작성하면 login 페이지에 액세스 할 때마다 LoginPrivateRoute의 함수가 실행되어 어떻게 작동하는지 결정합니다.
여기서 token이 없으면 element={
토큰이 있으면 토큰 sort를 확인하고 해당 페이지로 이동
여기서 LoginPrivateRoute는
이 부분이
// App.tsx
export default function App() {
return (
<Routes>
<Route element={<LoginPrivateRoute />}>
<Route index={true} element={<Login />} errorElement={<NotFound />} />
</Route>
</Routes>
);
}
3. PrivateRoute
나머지 PrivateRoute는
더 간단한 기능을 수행합니다.
토큰이 있는지 확인하는 경우 아울렛이 없으면 ‘/’로 보냅니다.
export function PrivateRoute(): JSX.Element {
const isAuthenticated = loginTokenExpiry();
return isAuthenticated ? <Outlet /> : <Navigate to="/" />;
}
3-2. PrivateRoute의 사용 예
이제 LoginPrivateRoute 1개와 PrivateRoute 2개가 있습니다.
앞으로 로그인, 메인 등 페이지로 이동할 때마다 3개 PrivateRoute가 어디로 보내는지 확인하고 적절한 위치로 이동합니다.
export default function App() {
return (
<Routes>
<Route element={<LoginPrivateRoute />}>
<Route index={true} element={<Login />} />
</Route>
<Route element={<PrivateRoute />}>
<Route element={<ProtectRoute access={"00"} />}>
<Route path="/management" element={<Management />} >
<Route index={true} element={<Admin />} />
<Route path="/management/register" element={<Register />} />
<Route path="/management/license" element={<License />} />
</Route>
</Route>
</Route>
<Route element={<PrivateRoute />}>
<Route element={<ProtectRoute access={"01"} />}>
<Route path="/user" element={<User />} >
<Route index={true} element={<UserAdmin />} />
<Route path="/user/register" element={<UserRegister />} />
<Route path="/user/account" element={<UserAccount />} />
</Route>
</Route>
</Route>
<Route path="*" element={<NotFound />} />
</Routes>
);
}
그 안에 ProtectRoute를 가지고, 그 안에 Route를, 그리고 그 안에 Route 복수를 가지는 것을 확인할 수 있습니다.
이 구조는 내가 궁극적으로 만들려는 형태입니다.
LoginPrivateRoute에서 토큰이 있다면 sort에 따라 페이지를 이동한다고 말했습니다.
각각 “/management”와 “/user”로 이동하지만, 이 두 URL을 가진 Route는 ProtectRoute를 가진 Route가 감싸고 PrivateRoute가 감싸고 있습니다.
구조가 이렇게 “/management”로 이동하면, “/management”를 감싸고 있는 최상위 Route인 PrivateRoute가 실행되고, 다음에 ProtectRoute를 가지는 Route가 실행되는 형태입니다.
아래 코드는 사실상 LoginPrivateRoute와 동일한 기능을 수행하는 PrivateRoute입니다.
isAuthenticated 반환 값이 true이면
4. Outlet이란?
그러면
router가 지원하는 기능입니다.
위의 사진과 같은 레이아웃이 있는 페이지가 있을 때 왼쪽 sideMenu가 계속 유지하면서 차트 영역만 계속 변경할 때 Management.tsx 마찬가지로 코드를 작성합니다.
다음 코드는 SideMenu 구성 요소가 정적으로 배치되는 동안 아울렛 영역만 변경됩니다.
변경되는 레이아웃은 다음 App.tsx에서 확인할 수 있습니다.
// Management.tsx
function Management() {
return (
<>
<SideMenu />
<Outlet />
</>
);
}
Management를 element로 갖는 Route가 있습니다.
이 루트 중 Admin, Register, License를 element로 가지며 path가 있는 Route가 다시 세 가지 있습니다.
이 3개의 Route(element)는 모두 Management 콘센트입니다.
Outlet이라는 컴포넌트 안에 3개의 컴포넌트(path)가 있어, 유저가 브라우저에 url 를 입력하면, 그 url 와 일치하는 Route 를 Outlet 가 return 합니다.
if 등을 사용하여 조건부로 렌더링하는 것과 같습니다.
그래서 쉽게 생각하면 아울렛은 라우터의 아이들입니다!
이런 식으로 알아야합니다.
자세한 내용은 공식 문서를 참조하십시오.
// App.tsx
export default function App() {
return (
<Route path="/management" element={<Management />} >
<Route index={true} element={<Admin />} />
<Route path="/management/register" element={<Register />} />
<Route path="/management/license" element={<License />} />
</Route>
);
}
5. ProtectRoute 및 Route index
다시 기존 코드로 돌아가서 두 개의 PrivateRoute를 살펴 보았습니다.
그건 그렇고 PrivateRoute의 경우,
이 루트는 권한이 있으면 관리자 페이지에, 그렇지 않으면 사용자 페이지에만 액세스할 수 있습니다.
아래 코드를 보면 ProtectRoute는 access를 매개 변수로 사용하고 token의 “sort”와 비교하여
sort가 access와 같으면 Outlook으로 이동합니다.
일치하지 않으면 sort에 맞는 페이지로 이동합니다.
그렇지 않지만 위의 내용에 해당하지 않는 경우 액세스할 수 없다는 메시지를 표시하는 빈 페이지로 이동합니다.
// ProtectRoute.tsx
interface PROTECT_ROUTE_TYPE {
access: string;
}
export default function ProtectRoute({ access }: PROTECT_ROUTE_TYPE): React.ReactElement | null {
const loginMember = JSON.parse(localStorage.getItem("token") as string);
if (loginMember.sort === access) return <Outlet />;
else {
if (loginMember.sort === "00") return <Navigate to="/management" />;
else if (loginMember.sort === "01") return <Navigate to="/user" />;
else return <Navigate to="/permission" />;
}
}
아래의 App.tsx를 보면 첫 번째 경로에 세 개의 경로가 있습니다.
이 중 path 가 없는 index 가 있는 Route 는, 「/management」로 액세스했을 경우에 나타내는 default 페이지입니다.
index를 사용하지 않는 경우 “/management”로 연결하면 sideMenu만 표시되고 아울렛 영역은 빈 페이지로 표시됩니다.
admin에 액세스하려면 /management/admin을 입력해야 합니다.
이 부분은 만들려는 기능에 따라 선택적으로 사용할 수 있습니다.
// App.tsx의 일부분
<Route path="/management" element={<Management />} >
<Route index={true} element={<Admin />} />
<Route path="/management/register" element={<Register />} />
<Route path="/management/license" element={<License />} />
</Route>
// index를 안쓸경우
<Route path="/management" element={<Management />} >
<Route path="/management/admin" element={<Admin />} />
<Route path="/management/register" element={<Register />} />
<Route path="/management/license" element={<License />} />
</Route>
최종 코드
다음 코드는 최종 코드입니다.
상기에 없었던 errorElement 라고 하는 것이 생겼습니다만, 이 컴퍼넌트는 그 페이지에 액세스 했습니다만, 어떤 이유로 error가 발상한다고 보여주는 페이지입니다.
// App.tsx
// 아래의 코드 url에는 파라미터와 쿼리스트링을 사용하지 않았습니다.
실제로는 사용해야합니다.
export default function App() {
return (
<Routes>
<Route element={<LoginPrivateRoute />}>
<Route index={true} element={<Login />} errorElement={<NotFound />} />
</Route>
<Route element={<PrivateRoute />}>
<Route element={<ProtectRoute access={"00"} />}>
<Route path="/management" element={<Management />} errorElement={<NotFound />}>
<Route index={true} element={<Admin />} errorElement={<NotFound />} />
<Route path="/management/register" element={<Register />} errorElement={<NotFound />} />
<Route path="/management/license" element={<License />} errorElement={<NotFound />} />
</Route>
</Route>
</Route>
<Route element={<PrivateRoute />}>
<Route element={<ProtectRoute access={"01"} />}>
<Route path="/user" element={<User />} errorElement={<NotFound />}>
<Route index={true} element={<UserAdmin />} errorElement={<NotFound />} />
<Route path="/user/register" element={<UserRegister />} errorElement={<NotFound />} />
<Route path="/user/account" element={<UserAccount />} errorElement={<NotFound />} />
</Route>
</Route>
</Route>
<Route path="*" element={<NotFound />} /> // 위에서 설정한 route외 접근시도 할 경우
</Routes>
);
}
// ProtectRoute.tsx
interface PROTECT_ROUTE_TYPE {
access: string;
}
export default function ProtectRoute({ access }: PROTECT_ROUTE_TYPE): React.ReactElement | null {
const loginMember = JSON.parse(localStorage.getItem("token") as string);
if (loginMember.sort === access) return <Outlet />;
else {
if (loginMember.sort === "00") return <Navigate to="/management" />;
else if (loginMember.sort === "01") return <Navigate to="/user" />;
else return <Navigate to="/permission" />;
}
}
// PrivateRoute.tsx
export function PrivateRoute(): JSX.Element {
const isAuthenticated = loginTokenExpiry();
return isAuthenticated ? <Outlet /> : <Navigate to="/" />;
}
export function LoginPrivateRoute() {
const isAuthenticated = loginTokenExpiry();
return isAuthenticated ? redirect() : <Outlet />;
}
const redirect = () => {
const loginMember = JSON.parse(localStorage.getItem("token") as string);
if (loginMember.sort === "00") return <Navigate to="/management" />;
else if (loginMember.sort === "01") return <Navigate to="/user" />;
else return <Navigate to="/permission" />;
};