๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿ”‘ Keycloak

[Keycloak, Docker, React] React์—์„œ keycloak์„ ๋” ์ƒ์„ธํ•˜๊ฒŒ ๋‹ค๋ค„๋ณด์ž.

by hyeong._.ing 2026. 4. 19.

 

 

์ „ํฌ์ŠคํŒ…์—์„œ keycloak๊ณผ React์˜
๊ธฐ๋ณธ์ ์ธ ์—ฐ๊ฒฐ๊นŒ์ง€ ํ–ˆ๋‹ค.
์ด๋ฒˆ์—” ํ† ํฐ์„ ๊ฐ€์ ธ์˜ค๊ณ ,
์‚ฌ์šฉ์ž์˜ ๊ถŒํ•œ์— ๋”ฐ๋ผ ํŽ˜์ด์ง€์˜ ์ถœ์ž…๊ตฌ๋ฅผ ๊ด€๋ฆฌํ•˜์ž.

 

 

 

 

[  Keycloak ์„ค์ •ํ•˜๊ธฐ  ]

 

 

[Keycloak, React, Docker] React์—์„œ Keycloak์„ ๋ณธ๊ฒฉ ์ ์šฉํ•˜๊ณ  ์ฝ”๋“œ๋ฅผ ํ•˜๋‚˜ํ•˜๋‚˜ ํŒŒํ—ค์ณ๋ณด์ž.

keycloak.js๋ฅผ ๋งŒ๋“ค์–ด์„œkeycloak ์„œ๋ฒ„์™€ ํ†ต์‹ ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ–ˆ๋‹ค.๋‹ค์Œ์€ ์ด ํ†ต์‹ ์„ ์ด์šฉํ•ด์„œ์›ํ•˜๋Š” ๋ฐฉํ–ฅ์œผ๋กœ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด ๋ณด์ž. [ Keycloak ์„ค์ •ํ•˜๊ธฐ ] [Keycloak, Docker, Mac] Keycloak์—์„œ client, role, users ๋“ฑ ์„ค

post-this.tistory.com

 

 

 

 

 

1. ํ๋ฆ„ ์„ค๋ช… (๋ณต์Šต์„ ํฌํ•จํ•ด์„œ)

  • keycloak.js์—์„œ keycloak ์„œ๋ฒ„์™€ ํ†ต์‹ ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ์ฒด๋ฅผ ํ•˜๋‚˜ ์ƒ์„ฑํ–ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋“ค์–ด์˜จ ์‚ฌ์šฉ์ž๊ฐ€ ํŽ˜์ด์ง€์— ๋กœ๊ทธ์ธ์„ ํ–ˆ๋Š”์ง€, ์•ˆํ–ˆ๋Š”์ง€ ํ™•์ธํ•˜๋Š” main.jsx๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค. ๋กœ๊ทธ์ธ์„ ํ–ˆ๋‹ค๋ฉด ๋กœ๊ทธ์ธ ํ•œ ์‚ฌ์šฉ์ž์˜ ์ •๋ณด๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๊ธฐ ์œ„ํ•œ AuthProvider.jsx๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค.

  • authFetch.js
    : ์„œ๋ฒ„์— ๋ฐ์ดํ„ฐ๋ฅผ ์š”์ฒญํ•  ๋•Œ ํ† ํฐ์„ ์ž๋™์œผ๋กœ ์ฑ™๊ฒจ์„œ ๋ณด๋‚ด๊ณ  ์œ ํšจ๊ธฐ๊ฐ„์„ ์ฒดํฌํ•œ๋‹ค.

  • ProtectedRoute.jsx
    : ํŠน์ •ํŽ˜์ด์ง€์— ๋“ค์–ด๊ฐ€๊ธฐ ์ „, ๋กœ๊ทธ์ธ ์—ฌ๋ถ€์™€ ๊ถŒํ•œ์„ ๊ฒ€์‚ฌํ•ด์„œ ์ž๊ฒฉ์ด ์—†์œผ๋ฉด ํŽ˜์ด์ง€์— ๋“ค์–ด์˜ค์ง€ ๋ชปํ•˜๋„๋ก ๋ง‰๋Š”๋‹ค.

  • App.jsx
    : ์–ด๋–ค ํŽ˜์ด์ง€๋ฅผ ๋ณด์•ˆ ๊ตฌ์—ญ์œผ๋กœ ์„ค์ •ํ•˜์ง€ ๊ฒฐ์ •ํ•œ๋‹ค.

 

 

 


 

 

 

 

2. autoFetch.js
์„œ๋ฒ„(๋ฐฑ์—”๋“œ)์—์„œ ์‚ฌ์šฉ์ž์˜ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•ด(loadMe), ํ† ํฐ์„ Keycloak.js์—์„œ ๊ฐ€์ ธ์™€ ์„œ๋ฒ„๋กœ ๋ณด๋‚ธ๋‹ค.

 

  • ์ „์ฒด ์ฝ”๋“œ
import keycloak from "../keycloak.js";

export async function authFetch(url, options = {}) {
    if (!keycloak.authenticated) {
        throw new Error("๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.");
    }

    await keycloak.updateToken(30);

    const headers = new Headers(options.headers || {});

    if (keycloak.token) {
        headers.set("Authorization", `Bearer ${keycloak.token}`);
    }

    return fetch(url, {
        ...options,
        headers,
    });
}

 

 

  • ์„ค๋ช…์ด ํฌํ•จ๋œ ์ฝ”๋“œ
import keycloak from "../keycloak.js";

// ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž์˜ ํ† ํฐ์„ ์„œ๋ฒ„์— ๋ณด๋‚ด์„œ ๊ทธ์— ๋งž๋Š” ๋ฐ์ดํ„ฐ ๋ฐ›๋Š”๋‹ค.
// ์ด๋•Œ keycloak.js๋กœ ๊ฐ€์„œ ํ† ํฐ์„ ๊ฐ€์ ธ์˜จ๋‹ค.
// AuthProvider๋Š” ๋กœ๊ทธ์ธํ•œ ์‚ฌ๋žŒ์— ๋Œ€ํ•œ ์ƒ์„ธ ์ •๋ณด๊ฐ€ ํ•„์š”ํ•˜๋‹ค.
// ๊ทธ๋ž˜์„œ authFetch๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค.
export async function authFetch(url, options = {}) {

    // ๋กœ๊ทธ์ธ์ด ๋˜์–ด์žˆ๋Š”์ง€ ํ™•์ธํ•œ๋‹ค.
    if (!keycloak.authenticated) {
        throw new Error("๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.");
    }

    // ํ† ํฐ ์œ ํšจ์‹œ๊ฐ„์ด 30์ดˆ ์ดํ•˜๋กœ ๋‚จ์•˜์œผ๋ฉด ํ† ํฐ ์—…๋ฐ์ดํŠธ ๋˜๋„๋ก ํ•œ๋‹ค.
    // ํ† ํฐ์˜ ์œ ํšจ๊ธฐ๊ฐ„์€ ๋ณดํ†ต 5๋ถ„์—์„œ 15๋ถ„์œผ๋กœ ์งง๋‹ค.
    // ํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜๋ฉด ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ฏธ๋ฆฌ ์ฒดํฌํ•ด์„œ ์ƒˆํ† ํฐ์„ ๋ฐ›์•„์˜จ๋‹ค.
    await keycloak.updateToken(30);

    // ํ—ค๋”์— ํ† ํฐ ๋„ฃ๋Š”๋‹ค.
    // ์˜ˆ๋กœ, ๊ด€๋ฆฌ์ž์—๊ฒŒ Read ๊ถŒํ•œ์ด ์žˆ๋Š” ๊ฒฝ์šฐ ๊ทธ๊ฒŒ ํ† ํฐ์— ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์„œ๋ฒ„๋Š” ์ฝ์„ ์ˆ˜ ์žˆ๋‹ค๊ณ  ํŒ๋‹จํ•œ๋‹ค.
    // ์ด๋ ‡๊ฒŒ Bearer ํ† ํฐ ์‹œ์Šคํ…œ์ด ์žˆ์–ด์•ผ ์ฃผ์†Œ์ฐฝ์„ ํ†ตํ•ด ๊ณ ๊ฐ ์ •๋ณด๋ฅผ ๋ณด๋Š” ๊ฑธ ๋ง‰์„ ์ˆ˜ ์žˆ๋‹ค.
    // options.headers || {}๋Š” option ์•ˆ์— headers๊ฐ€ ์žˆ์œผ๋ฉด ๊ทธ๊ฑฐ ์“ฐ๊ณ , ์—†์œผ๋ฉด ๋นˆ ๊ฐ์ฒด๋ฅผ ์“ฐ๋ผ๋Š” ๋œป์ด๋‹ค.
    const headers = new Headers(options.headers || {});

    // keycloak.token์—์„œ ์•Œ์•„์„œ ํ—ค๋”์— ๋ณต์žกํ•œ ํ† ํฐ์„ ๋ถ™์—ฌ์ฃผ๋„๋ก ํ•œ๋‹ค.
    // "Authorization"์€ HTTP ํ”„๋กœํ† ์ฝœ์—์„œ ์ •ํ•ด๋‘” key๊ณ , ์ด key์— ๋‚ด์šฉ์„ ๋„ฃ์œผ๋ฉด ๋น„๋ฐ€๋ฒˆํ˜ธ ํ˜น์€ ํ† ํฐ์„ ์˜๋ฏธํ•œ๋‹ค.
    // `Bearer ${keycloak.token}`๋Š” ๋ฐ์ดํ„ฐ ๋ถ€๋ถ„์ด๋‹ค.
    // Bearer ๋’ค์— tokendl ๋ถ™์„ ๊ฒƒ์ด๋‹ค.
    // Authorization: Bearer abc123xyz ์ด๋Ÿฐ ๋А๋‚Œ์œผ๋กœ...
    if (keycloak.token) {
        headers.set("Authorization", `Bearer ${keycloak.token}`);
    }

    // ์„œ๋ฒ„๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์œผ๋Ÿฌ ๊ฐ„๋‹ค.
    // option์„ ํฌํ•จ์‹œ์ผœ์„œ ๋ชจ๋“  ์š”์ฒญ์—์„œ ํ•จ์ˆ˜๋ฅผ ๊ทธ๋Œ€๋กœ ์“ฐ๋„๋ก ํ•œ๋‹ค.
    return fetch(url, {
        ...options,
        headers,
    });
}
authFetch๋Š” ํ† ํฐ์„ ์ž๋™์œผ๋กœ ๋ถ€์ฐฉํ•ด์„œ ๋ณด๋‚ด๋Š” ์—ญํ• ์„ ํ•œ๋‹ค. ์ด ํŒŒ์ผ์„ ์ž‘์„ฑํ•˜์ง€ ์•Š๋Š”๋‹ค๊ณ  ํ•˜๋ฉด ๊ณ ๊ฐ ์ •๋ณด๊ฐ€ ํ•„์š”ํ•œ ๊ณณ์— ๋งค๋ฒˆ ํ† ํฐ์„ ์š”์ฒญํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด์•ผํ•œ๋‹ค. 

 

 

Q. ํ† ํฐ์„ ์ด์šฉํ•ด์„œ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค๊ณ  ํ–ˆ๋Š”๋ฐ, ์ด๋ฏธ ํ† ํฐ์— ์‚ฌ์šฉ์ž ์ •๋ณด๊ฐ€ ์žˆ๋Š” ๊ฒƒ ์•„๋‹Œ๊ฐ€? ํ† ํฐ์— ์žˆ๋Š” ์ •๋ณด๋กœ ํŒ๋‹จํ•˜๋ฉด ๋˜๋Š”๊ฑฐ์ง€, ๊ตณ์ด ์„œ๋ฒ„์— ๋ฐ์ดํ„ฐ๋ฅผ ์š”์ฒญํ•ด์„œ ๋ฐ›์•„์˜ค๋Š” ์ด์œ ๊ฐ€ ๋ญ˜๊นŒ?

A. ํ† ํฐ์˜ ์œ ํšจ๊ธฐ๊ฐ„์„ 15๋ถ„์ด๋ผ๊ณ  ๊ฐ€์ •ํ•˜์ž. ๊ทธ๋Ÿฌ๋ฉด ์ด ์œ ํšจ๊ธฐ๊ฐ„ ๋™์•ˆ์—” ํ† ํฐ ์† ๋‚ด์šฉ์ด ๋ณ€ํ•˜์ง€ ์•Š๋Š”๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ํ† ํฐ์˜ ์œ ํšจ๊ธฐ๊ฐ„์ด ๋‹ค ๊ฐ€๊ธฐ ์ „, ์‚ฌ์šฉ์ž์˜ ๊ถŒํ•œ์ด ๋ฐ”๋€Œ์—ˆ๋‹ค. ํ† ํฐ ์† ๋‚ด์šฉ์€ 15๋ถ„ ๋™์•ˆ ์œ ํšจํ•˜๋‹ˆ ํ† ํฐ์˜ ๋‚ด์šฉ์„ ๊บผ๋‚ด์„œ ์“ฐ๋ฉด ๋ถ„๋ช… ์ทจ์†Œ๋œ ๊ถŒํ•œ์ด๋ผ๋„ ์‚ฌ์šฉ์ž๋Š” ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ์ด๋‹ค. ๊ทธ๋ž˜์„œ ์ด๋Ÿฐ ์ ๋“ค์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ์„œ๋ฒ„์— ๋ฌผ์–ด๋ณด๋„๋ก ์œ ๋„ํ•œ ๊ฒƒ์ด๋‹ค. ํ† ํฐ์˜ ๋‚ด์šฉ์€ ๋ณ€ํ•˜์ง€ ์•Š๋”๋ผ๋„, ์ด ํ† ํฐ์„ ๊ฐ€์ง€๊ณ  ์„œ๋ฒ„์—๊ฒŒ ์‚ฌ์šฉ์ž์˜ ์ •๋ณด๋ฅผ ์–ป์–ด์˜ค๋ฉด ๊ทธ ์ •๋ณด์—๋Š” ๊ถŒํ•œ์ด ๋ณ€๊ฒฝ๋˜์–ด์žˆ์„ ๊ฒƒ์ด๋‹ค.

ํ˜„์žฌ ํ”„๋กœ์ ํŠธ์—๋Š” ์„œ๋ฒ„์— ์š”์ฒญํ•ด์•ผํ•˜๋Š” ๋” ํฐ ์ด์œ ๊ฐ€ ์žˆ๋‹ค. ์ผ๋‹จ keycloak์„ ์‚ฌ์šฉํ•œ ํ”„๋กœ์ ํŠธ์— ์‚ฌ์šฉ์ž๋งˆ๋‹ค ์„ธ์„ธํ•œ ๊ถŒํ•œ์„ ์„ค์ •ํ–ˆ๋‹ค. ์ฝ๊ธฐ ๊ถŒํ•œ / ์ถ”๊ฐ€ ๊ถŒํ•œ / ๊ฒ€์ƒ‰ ๊ถŒํ•œ ๋“ฑ๋“ฑ... Keycloak ์„œ๋ฒ„๋Š” ์ด๋Ÿฐ ์„ธ์„ธํ•œ ๊ถŒํ•œ๋“ค๊นŒ์ง€ ๋ชจ๋ฅธ๋‹ค. ๊ทธ๋ž˜์„œ Keycloak์€ "์ด ์‚ฌ์šฉ์ž๋Š” ์ตœ๊ณ ๊ด€๋ฆฌ์ž ์—ญํ• ์ด์•ผ."๋ฅผ ์•Œ๋ ค์ค€๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ์„œ๋ฒ„๋Š” ๊ทธ๊ฑธ ๋ฐ›์•„์„œ "์šฐ๋ฆฌ ์„œ๋น„์Šค์—์„œ ์ตœ๊ณ ๊ด€๋ฆฌ์ž๋Š” ๋ชจ๋“  ๊ถŒํ•œ์„ ํ—ˆ์šฉํ•˜๊ณ  ์žˆ์–ด."๋ผ๊ณ  ์ƒ์„ธ ์ •๋ณด๋ฅผ ๋ฐ›์•„์˜จ๋‹ค.

 

 

 


 

 

 

 

3. ProtectedRoute.jsx

ํŽ˜์ด์ง€์— ๋“ค์—ฌ๋ณด๋‚ด๊ธฐ ์ „, ๋ˆ„๊ฐ€ ๋“ค์–ด๊ฐˆ ์ˆ˜ ์žˆ๋Š”์ง€ ๋ณด์•ˆ ๊ทœ์น™์„ ์ •์˜ํ•œ๋‹ค.

 

  • ์ „์ฒด ์ฝ”๋“œ
import { Navigate, useLocation } from "react-router-dom";
import { useAuth } from "./AuthProvider.jsx";

export default function ProtectedRoute({ children, requiredRole, requireCustomerAccess = false }) {
    const location = useLocation();
    const auth = useAuth(); 

    if (auth.loading) {
        return (
            <div style={{ padding: 24, textAlign: "center" }}>
                ๊ถŒํ•œ ํ™•์ธ ์ค‘์ž…๋‹ˆ๋‹ค.
            </div>
        );
    }

    if (!auth.isAuthenticated) {
        const redirect = encodeURIComponent(location.pathname);
        return <Navigate to={`/login?redirect=${redirect}`} replace />;
    }

    if (requiredRole === "SUPER_ADMIN" && !auth.superAdmin) {
        return <Navigate to="/" replace />;
    }

    if (requireCustomerAccess) {
        const canAccessCustomerPage =
            auth.superAdmin ||
            auth.customerRead ||
            auth.customerSearch ||
            auth.customerAdd ||
            auth.customerEdit ||
            auth.customerDelete;

        if (!canAccessCustomerPage) {
            return <Navigate to="/" replace />;
        }
    }

    return children;
}

 

 

  • ์„ค๋ช…์ด ํฌํ•จ๋œ ์ฝ”๋“œ
import { Navigate, useLocation } from "react-router-dom";
import { useAuth } from "./AuthProvider.jsx";

// ํŠน์ • ํŽ˜์ด์ง€์— ๋“ค์—ฌ๋ณด๋‚ด๊ธฐ ์œ„ํ•ด์„œ ๋กœ๊ทธ์ธ์€ ํ–ˆ๋Š”์ง€, ๊ถŒํ•œ์€ ์žˆ๋Š”์ง€ ๊ฒ€์‚ฌํ•œ๋‹ค.
// requiredRole , requireCustomerAccess๋Š” ์กฐ๊ฑด์ด๋‹ค.
// ๋’ค์— ๋‚˜์˜ฌ App.jsx์—์„œ ํ•ด๋‹น ์กฐ๊ฑด์„ ํฌํ•จ์‹œํ‚ค๋ฉด ํ•„์ˆ˜๋กœ ํ™•์ธํ•˜์ง€๋งŒ
// ๊ทธ๋ ‡์ง€ ์•Š์„ ๊ฒฝ์šฐ false๊ฐ€ ๋‹ด๊ฒจ ๋„˜์–ด๊ฐ€๊ฒŒ ๋œ๋‹ค.
export default function ProtectedRoute({ children, requiredRole, requireCustomerAccess = false }) {
    
    const location = useLocation(); // ๊ฐ€๊ณ ์ž ํ•˜๋Š” ํŽ˜์ด์ง€
    const auth = useAuth(); // ๋‚ด ๋กœ๊ทธ์ธ ์ •๋ณด ์„œ๋ฒ„์—์„œ ๊บผ๋‚ด์˜จ ๊ฒƒ

    if (auth.loading) {
        return (
            // ์„œ๋ฒ„์—์„œ ๊ถŒํ•œ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ  ์žˆ๋Š” ์ค‘์ธ ๊ฒฝ์šฐ ๋กœ๋”ฉ ํ™”๋ฉด์„ ๋„์šด๋‹ค.
            <div style={{ padding: 24, textAlign: "center" }}>
                ๊ถŒํ•œ ํ™•์ธ ์ค‘์ž…๋‹ˆ๋‹ค.
            </div>
        );
    }

    // ๋กœ๊ทธ์ธ์„ ์•ˆํ•œ ๊ฒฝ์šฐ
    // ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ๋ณด๋‚ธ ๋’ค, ์›๋ž˜ ๊ฐ€๋ ค๊ณ  ํ–ˆ๋˜ ํŽ˜์ด์ง€๋ฅผ ๊ธฐ์–ตํ•ด๋‘”๋‹ค.
    if (!auth.isAuthenticated) {
        // ์—ฌ๊ธฐ์„œ ๊ฐ€๋ ค๋˜ ํŽ˜์ด์ง€๋ฅผ ๊ธฐ์–ตํ•ด๋‘๊ณ 
        const redirect = encodeURIComponent(location.pathname);
        // ๋กœ๊ทธ์ธ ๋’ค, ๊ฐ€๋ ค๋˜ ํŽ˜์ด์ง€๋กœ ๋ณด๋‚ด์ค€๋‹ค.
        return <Navigate to={`/login?redirect=${redirect}`} replace />;
    }

    // ์ตœ๊ณ ๊ด€๋ฆฌ์ž์ธ์ง€ ๊ฒ€์‚ฌํ•˜๊ณ , ๋งŒ์•ฝ ์•„๋‹ˆ๋ฉด ๋ฉ”์ธ ํŽ˜์ด์ง€๋กœ ๋ณด๋‚ธ๋‹ค.
    if (requiredRole === "SUPER_ADMIN" && !auth.superAdmin) {
        return <Navigate to="/" replace />;
    }

    // ๊ณ ๊ฐ ํŽ˜์ด์ง€์— ๋“ค์–ด๊ฐ€๋ ค๋Š” ๊ฒฝ์šฐ, ๋งŒ์•ฝ ๊ถŒํ•œ์ด ํ•˜๋‚˜๋„ ์—†์œผ๋ฉด ๋“ค์–ด๊ฐ€์ง€ ๋ชปํ•˜๊ฒŒ ๋ง‰๋Š”๋‹ค.
    if (requireCustomerAccess) {
       
       // ๊ถŒํ•œ์ด ์žˆ๋Š”์ง€ ํ™•์ธํ•œ๋‹ค.
        const canAccessCustomerPage =
            auth.superAdmin ||
            auth.customerRead ||
            auth.customerSearch ||
            auth.customerAdd ||
            auth.customerEdit ||
            auth.customerDelete;

        // ๊ถŒํ•œ์ด ์—†๋Š” ๊ฒฝ์šฐ ๋ฉ”์ธํŽ˜์ด์ง€๋กœ ๋ณด๋‚ธ๋‹ค.
        if (!canAccessCustomerPage) {
            return <Navigate to="/" replace />;
        }
    }
    
    // ์›๋ž˜ ๊ฐ€๋ ค๋˜ ํŽ˜์ด์ง€๋กœ ๋ณด๋‚ธ๋‹ค.
    // children์ด ๊ฐ€๊ณ ์ž ํ•˜๋Š” ํŽ˜์ด์ง€์ด๋‹ค.
    return children;
}
<Route path="/permission" element={
    <ProtectedRoute requiredRole="SUPER_ADMIN"> 
        <PermissionPage />
    </ProtectedRoute>
} />


์œ„์˜ ์ฝ”๋“œ๋Š” App.jsx์˜ ์ผ๋ถ€๋ถ„์ด๋‹ค. ProtectedRoute๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด์„œ ์กฐ๊ฑด์„ ๋ถ™์˜€๋‹ค. ๊ทธ๋ ‡๋‹ค๋ฉด PermissionPage๋Š” requiredRole="SUPER_ADMIN"์ด ํ•„์š”ํ•œ ๊ฒƒ์ด๋‹ค. ์‰ฝ๊ฒŒ ์„ค๋ช…ํ•ด์„œ SUPER_ADMIN์ด ์•„๋‹ˆ๋ฉด ์ด ํŽ˜์ด์ง€๋Š” ๋“ค์–ด๊ฐˆ ์ˆ˜ ์—†๋‹ค๋Š” ์˜๋ฏธ์ด๋‹ค.  ๋งŒ์•ฝ ํ•ด๋‹น ํŽ˜์ด์ง€์— ์–ด๋–ค ์กฐ๊ฑด๋„ ์•ˆ๋ถ™๋Š”๋‹ค๋ฉด ๊ทธ๋ƒฅ false๊ฐ€ ๋ถ™์–ด์„œ ์กฐ๊ฑด ์ƒ๊ด€์—†์ด ๋“œ๋‚˜๋“ค ์ˆ˜ ์žˆ๋‹ค๋Š” ์˜๋ฏธ๊ฐ€ ๋œ๋‹ค. ProtectedRoute์—์„œ false๋ฅผ ์„ค์ •ํ•˜์ง€ ์•Š์•˜๋‹ค๋ฉด, ์กฐ๊ฑด์ด ๋”ฑํžˆ ํ•„์š”ํ•˜์ง€ ์•Š๋Š” ํŽ˜์ด์ง€์—๋„ requiredRole=false๋ฅผ ๋ถ™์—ฌ์•ผํ•˜๋Š” ๋ฒˆ๊ฑฐ๋กœ์›€์ด ์žˆ๋‹ค.

๋งจ ๋ฐ‘์— return์„ ๋ณด๋ฉด children์„ ๋ฐ˜ํ™˜ํ•˜๊ณ  ์žˆ๋‹ค. ์™œ children์ด๋ผ๊ณ  ์ ์€๊ฑธ๊นŒ? App.jsx์˜ ์ฝ”๋“œ ์ผ๋ถ€๋ถ„์„ ๋‹ค์‹œ ์‚ดํŽด๋ณด์ž. ์—ฌ๊ธฐ์„œ ํŽ˜์ด์ง€๊ฐ€ <ProtectedRoute> <PremissionPage /> </ProtectedRoute> ๋ผ๊ณ  ๊ฐ์‹ธ์ ธ ์žˆ๋‹ค. ์ด๋Ÿฐ ํ˜•ํƒœ๋ฅผ children์ด๋ผ๊ณ  ๋ถ€๋ฅธ๋‹ค. ๋ฆฌ์•กํŠธ์—์„œ children์€ ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ž์‹ ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ’ˆ์„ ๋•Œ ์‚ฌ์šฉ๋˜๋Š” ์ด๋ฆ„์ด๋‹ค. 

์ผ๋ จ์˜ ๊ณผ์ •์„ ํ•œ ๋ฒˆ ์‚ดํŽด๋ณด์ž. ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธ์„ ์•ˆ ํ•œ ์ƒํƒœ๋กœ ๊ณ ๊ฐ๊ด€๋ฆฌํŽ˜์ด์ง€(/admin/customers)๋ฅผ ํด๋ฆญํ–ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ProtectedRoute์—์„œ ๋กœ๊ทธ์ธ์ด ์•ˆ๋˜์–ด์žˆ์œผ๋‹ˆ ๋กœ๊ทธ์ธ์„ ํ•˜๋ผ๊ณ  ๋‚ด๋ณด๋‚ธ๋‹ค. ์ด๋•Œ  ProtectedRoute๋Š” /admin/customers๋ฅผ ๊ธฐ์–ตํ•ด๋‘”๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธ์„ ๋งˆ์น˜๋ฉด ๊ธฐ์–ตํ•ด๋‘” /admin/customers๋กœ ๋‹ค์‹œ ๋ณด๋‚ด์ฃผ๊ณ  ๊ถŒํ•œ์„ ํ™•์ธํ•œ ๋’ค, children(<Customerpage />๋ฅผ ํ™”๋ฉด์— ๋ณด์—ฌ์ค€๋‹ค.

 

 

 

 


 

 

 

 

4. App.jsx
authFetch๋กœ ๊ฐ€์ ธ์˜จ ์ •๋ณด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ, ProtectedRoute๋ฅผ ํ™œ์šฉํ•ด ๊ฐ ํŽ˜์ด์ง€๋ฅผ ๊ฐ์‹ธ๊ณ  ์ „์ฒด ๊ฒฝ๋กœ๋ฅผ ์™„์„ฑํ•œ๋‹ค.

 

  • ์ „์ฒด ์ฝ”๋“œ
import {BrowserRouter, Routes, Route, Navigate} from "react-router-dom";
import VipCode from "./invitecode/VipCode.jsx";
import CustomerPage from "./customer/CustomerPage.jsx";
import Login from "./admin/adminlogin/Login.jsx";
import VIP from "./invitecode/page/VipPage.jsx";
import VVIP from "./invitecode/page/VvipPage.jsx";
import DIAMOND from "./invitecode/page/DiamondPage.jsx";
import ProtectedRoute from "./auth/ProtectedRoute.jsx";
import MainPage from "./home/MainPage.jsx";
import PermissionPage from "./admin/adminsetting/PermissionPage.jsx";

export default function App() {
    return (
        <BrowserRouter>
            <Routes>

                <Route path="/" element={<MainPage />} />

                <Route path="/invite" element={<VipCode />} />
                <Route path="/vip" element={<VIP />} />
                <Route path="/vvip" element={<VVIP />} />
                <Route path="/diamond" element={<DIAMOND />} />

                <Route path="/login" element={<Login />} />


                <Route
                    path="/admin/customers"
                    element={
                        <ProtectedRoute>
                            <CustomerPage />
                        </ProtectedRoute>
                    }
                />

                <Route
                    path="/permission"
                    element={
                        <ProtectedRoute requiredRole="SUPER_ADMIN">
                            <PermissionPage />
                        </ProtectedRoute>
                    }
                />

                <Route path="*" element={<Navigate to="/" replace />} />
            </Routes>
        </BrowserRouter>
    );
}

 

 

  • ์„ค๋ช…์ด ํฌํ•จ๋œ ์ฝ”๋“œ
import {BrowserRouter, Routes, Route, Navigate} from "react-router-dom";
import VipCode from "./invitecode/VipCode.jsx";
import CustomerPage from "./customer/CustomerPage.jsx";
import Login from "./admin/adminlogin/Login.jsx";
import VIP from "./invitecode/page/VipPage.jsx";
import VVIP from "./invitecode/page/VvipPage.jsx";
import DIAMOND from "./invitecode/page/DiamondPage.jsx";
import ProtectedRoute from "./auth/ProtectedRoute.jsx";
import MainPage from "./home/MainPage.jsx";
import PermissionPage from "./admin/adminsetting/PermissionPage.jsx";

export default function App() {
    return (
        <BrowserRouter>
            <Routes>

                {/* ๊ณต๊ฐœ ํŽ˜์ด์ง€ */}
                {/* ProtectedRoute๋กœ ๊ฐ์‹ธ์ ธ์žˆ์ง€ ์•Š์œผ๋ฉด ๋ˆ„๊ตฌ๋‚˜ ํŽ˜์ด์ง€ ๋ฐฉ๋ฌธ ๊ฐ€๋Šฅ */}
                <Route path="/" element={<MainPage />} />

                <Route path="/invite" element={<VipCode />} />
                <Route path="/vip" element={<VIP />} />
                <Route path="/vvip" element={<VVIP />} />
                <Route path="/diamond" element={<DIAMOND />} />

                {/* ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€ */}
                <Route path="/login" element={<Login />} />

                {/* ๊ด€๋ฆฌ์ž ํŽ˜์ด์ง€: ๋กœ๊ทธ์ธ ํ•„์š”ํ•จ */}
                <Route
                    path="/admin/customers"
                    element={
                        <ProtectedRoute>
                            <CustomerPage />
                        </ProtectedRoute>
                    }
                />

                {/* ๊ถŒํ•œ ๊ด€๋ฆฌ ํŽ˜์ด์ง€: SUPER_ADMIN๋งŒ */}
                {/* ์—ฌ๊ธฐ๋Š” ์—ญํ• ์ด SUPER_ADMIN๋งŒ ๋“ค์–ด๊ฐˆ ์ˆ˜ ์žˆ๋‹ค. */}
                <Route
                    path="/permission"
                    element={
                        <ProtectedRoute requiredRole="SUPER_ADMIN">
                            <PermissionPage />
                        </ProtectedRoute>
                    }
                />

                {/* ์ž˜๋ชป๋œ ์ฃผ์†Œ๋Š” ๋ฉ”์ธ์œผ๋กœ */}
                <Route path="*" element={<Navigate to="/" replace />} />
            </Routes>
        </BrowserRouter>
    );
}
์„œ๋ฒ„๋ฅผ ์—ด ๊ฒƒ๋„ ์•„๋‹ˆ๊ณ , ๊ฐ„๋‹จํ•œ ํŽ˜์ด์ง€์— ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ ํŽ˜์ด์ง€๋„ ๋”ฑํžˆ ์—†์–ด์„œ BrowserRouter๋กœ ๊ฐ์ŒŒ๋‹ค.(์‚ฌ์‹ค ์ด๊ฒƒ๋ฐ–์— ๋ชฐ๋ž์Œ) ๊ทธ๋Ÿฐ๋ฐ ์š”์ƒˆ๋Š” DataAPI ๋ฐฉ์‹์ธ createBrowserRouter๋กœ ๋งŽ์ด ์“ด๋‹ค๊ณ  ํ•œ๋‹ค. ๋ฌด์Šจ ์ฐจ์ด๊ฐ€ ์žˆ๋Š”์ง€๋Š” ๋‹ค์Œ ํฌ์ŠคํŒ…์—์„œ ๋‹ค๋ค„๋ณด๊ฒ ๋‹ค.

 

 

 


 

 

[  browserRouter์™€ createBrowserRouter์˜ ์ฐจ์ด์   ]

 

 

[React] BrowserRouter์™€ createBrowserRouter์˜ ํŠน์ง•๊ณผ ์ฝ”๋“œ์ž‘์„ฑ์ฐจ์ด

ํ”„๋กœ์ ํŠธ ์ดํ›„ ์ฝ”๋“œ๋ฅผ ๊ณต๋ถ€ํ•˜๋‹ค๊ฐ€createBrowserRouter์— ๋Œ€ํ•ด ์•Œ๊ฒŒ๋˜์—ˆ๋‹ค.๋‘ ๋ฐฉ์‹์— ์–ด๋–ค ์ฐจ์ด์ ์ด ์žˆ๋Š”์ง€ ์‚ดํŽด๋ณด์ž. 1. _ ์ปดํฌ๋„ŒํŠธ ๊ธฐ๋ฐ˜ ๋ผ์šฐํŒ…์•ฑ์ด ์ฃผ์†Œ์ฐฝ์˜ ๋ณ€ํ™”๋ฅผ ๊ฐ์ง€ํ•˜๊ณ  ๊ทธ์— ๋งž๋Š” ํ™”๋ฉด์„ ๋ณด์—ฌ

post-this.tistory.com