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

[React, Docker, VITE] Keycloak์ด ํ”„๋ก ํŠธ ์ฝ”๋“œ์— ์–ด๋–ป๊ฒŒ ์ ์šฉ๋˜๋Š” ๊ฑธ๊นŒ?

by hyeong._.ing 2026. 5. 1.

 

 

Keycloak์„ ์‚ฌ์šฉํ•˜๋Š”๋ฐ
ํ•„์š”ํ•œ ๊ธฐ์ดˆ ํŒŒ์ผ์„ ๋Œ€๋ถ€๋ถ„ ์ž‘์„ฑํ–ˆ๋‹ค.
๊ทธ๋Ÿฌ๋ฉด ๋งŒ๋“  ํŒŒ์ผ์„ ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉํ•˜๋Š”๊ฑธ๊นŒ?

 

 

 

 

 

[  ์ด์ „ ํฌ์ŠคํŒ… - keycloak์˜ ์„ค์ •ํŒŒ์ผ ์ž‘์„ฑํ•˜๊ธฐ  ]

 

 

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

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

post-this.tistory.com

 

 

 

 

1. PemissionSetting.jsx_ ๊ด€๋ฆฌ์ž๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ํŽ˜์ด์ง€

  • ์ด ํŽ˜์ด์ง€๋Š” ์ผ๋ฐ˜ admin์„ ๊ด€๋ฆฌํ•˜๋Š” ํŽ˜์ด์ง€๋กœ, super admin๋งŒ ๋“ค์–ด๊ฐˆ ์ˆ˜ ์žˆ๋„๋ก ํ•˜๊ณ  ์žˆ๋‹ค.
  • ์ด ํŽ˜์ด์ง€์— ๋“ค์–ด๊ฐ€๊ธฐ ์œ„ํ•ด์„œ Keycloak์œผ๋กœ ๋กœ๊ทธ์ธ์„ ํ•ด์•ผํ•œ๋‹ค.
  • ์‚ฌ์šฉ์ž์˜ ์—ญํ• ์ด super admin์ธ์ง€ ํ™•์ธ๋˜์–ด์•ผ ๊ด€๋ฆฌ์ž ์ •๋ณด๊ฐ€ ์ถœ๋ ฅ๋˜๋„๋ก ํ•œ๋‹ค.
  •  keycloak๊ณผ ๊ด€๋ จ๋œ ํŒŒ์ผ๋“ค importํ•˜๊ธฐ
// Keycloak๊ณผ ๊ด€๋ จ๋œ ํŒŒ์ผ์„ import ํ–ˆ๋‹ค.
// authFetch๋Š” Token์„ ํ—ค๋”์— ๋ถ™์ด๋Š” ์—ญํ• ์„ ํ–ˆ๋‹ค.
import { authFetch } from "../../auth/authFetch.js";
// useAuth๋Š” keycloak์˜ ์ƒํƒœ(๋กœ๊ทธ์ธํ–ˆ๋Š”์ง€, ๋ˆ„๊ตฌ์ธ์ง€)๋ฅผ ๋ฆฌ์•กํŠธ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์•Œ ์ˆ˜ ์žˆ๊ฒŒ ํ•œ๋‹ค.
import { useAuth } from "../../auth/AuthProvider.jsx";

 

  • ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•ด auth ์„ ์–ธ
export default function PermissionSetting() {
    // useAuth ์‚ฌ์šฉํ•˜๊ฒ ๋‹ค!
    // useAuth๋Š” AuthContext๋ผ๋Š” ๋ฐ์ดํ„ฐ ์ €์žฅ์†Œ์— ์ ‘๊ทผ๊ฐ€๋Šฅํ•จ
    const auth = useAuth();

 

 

 

Q. useAuth๊ฐ€ ์–ด๋–ป๊ฒŒ AuthContext์— ์ ‘๊ทผ๊ฐ€๋Šฅํ•œ๊ฐ€?

A. useAuth๋Š” AuthProvider๊ฐ€ ๊ฐ€์ ธ์˜จ keycloak ์ •๋ณด๋“ค์„ AuthContext์— ์ง‘์–ด๋„ฃ์—ˆ๋‹ค. ๊ทธ๋ž˜์„œ useAuth๋Š” ๊ทธ๋Œ€๋กœ ๊บผ๋‚ด ์“ด๋‹ค. AuthProvider์˜ ์ฝ”๋“œ๋ฅผ ์‚ดํŽด๋ณด๋ฉด ๋” ์‰ฝ๊ฒŒ ์ดํ•ด๊ฐ€๋Šฅํ•˜๋‹ค.
// AuthProvider ์ฝ”๋“œ ์ผ๋ถ€
// AuthProvider๊ฐ€ keycloak ์ •๋ณด๋ฅผ ๋„ฃ์—ˆ๋‹ค.(AuthContext)
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;โ€‹

 

    // useAuth ์ฝ”๋“œ ์ผ๋ถ€
    // useAuth๊ฐ€ ๋“ค๊ณ ์˜จ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ๊ฐ„๋‹ค.(AuthContext๋ฅผ context๋กœ ๊ฐ€์ ธ์˜ด)
    const context = useContext(AuthContext);
    return context;

 

 

  • ๊ด€๋ฆฌ์ž ํŽ˜์ด์ง€๋ฅผ ๋ณด์—ฌ์ฃผ๊ธฐ ์œ„ํ•œ ์กฐ๊ฑด๋ฌธ
    useEffect(() => {
        // auth.loading์€ ํ‚คํด๋ก์ด ์„œ๋ฒ„์™€ ํ†ต์‹ ํ•˜๋ฉด์„œ ์‚ฌ์šฉ์ž๊ฐ€ ๋ˆ„๊ตฌ์ธ์ง€ ํ™•์ธ ์ค‘์ธ ์ƒํƒœ์ด๋‹ค.
        // auth.loading์€ ๋กœ๋”ฉ ์ค‘์ด ์•„๋‹Œ์ง€ ๋ฌป๋Š”๋‹ค. true -> ์กฐํšŒ์ค‘ false -> ์กฐํšŒ๋
        // auth.superAdmin์€ ํ‚คํด๋ก ํ† ํฐ ์•ˆ์— ๋“ค์–ด์žˆ๋Š” Role ์ •๋ณด๋ฅผ ํ™•์ธํ•ด์„œ ์ด ์‚ฌ๋žŒ์ด ์ตœ๊ณ ๊ด€๋ฆฌ์ž์ธ์ง€ ํŒ๋‹จํ•œ๋‹ค.
        // keycloak์ด ํ™•์ธ์„ ํ•œ ๋’ค ๊ทธ ๊ฒฐ๊ณผ๊ฐ€ ์ตœ๊ณ  ๊ด€๋ฆฌ์ž์ผ ๋•Œ๋งŒ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๋กœ์ง์ด๋‹ค.
        if (!auth.loading && auth.superAdmin) {
            // ๋ชจ๋‘ true๊ฐ€ ๋‚˜์˜ค๋ฉด loadAmdins๊ฐ€ ์‹คํ–‰๋˜์–ด ๊ด€๋ฆฌ์ž ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค.
            loadAdmins();
        }
    }, [auth.loading, auth.superAdmin]); // ์ด ๋‘ ๊ฐ’์ด ๋ณ€ํ• ๋•Œ๋งˆ๋‹ค ๋กœ์ง ๋‹ค์‹œ ์ฒดํฌํ•˜๊ธฐ

 

Q. !auth.loading์„ ์“ด ์ด์œ ๊ฐ€ ๋ญ˜๊นŒ? ๊ทธ๋ƒฅ auth.superAmdin๋งŒ ๊ฒ€์‚ฌํ•˜๋ฉด ๋˜๋Š”๊ฑฐ ์•„๋‹๊นŒ?

A. !auth.loading์€ ํ™•์‹คํ•œ ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์˜ฌ ๋•Œ๊นŒ์ง€ ํ™”๋ฉด์„ ๋ณด์—ฌ์ฃผ์ง€ ์•Š๊ณ  ๊ธฐ๋‹ค๋ฆฌ๊ฒŒ ํ•œ๋‹ค. ๊ทธ๋‹ˆ๊นŒ ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธ์ด ์ œ๋Œ€๋กœ ๋๋Š”์ง€ ์™„์ „ํžˆ ํ™•์ธ์ด ๋๋‚˜๋ฉด ๊ทธ ํ›„์— ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž๊ฐ€ superAdmin์ด ๋งž๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๋กœ์ง์„ ๊ฐ€์ง„๋‹ค. !auth.loading์„ ์•ˆ์ ์œผ๋ฉด ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธ์ด ๋˜์–ด์žˆ๋Š” ์ƒํƒœ์ธ๋ฐ๋„ keycloak๊ณผ ์„œ๋ฒ„์˜ ์ „๋‹ฌ์ด ์™„๋ฒฝํ•˜๊ฒŒ ์ด๋ค„์ง€์ง€ ์•Š์•„์„œ isAuthenticated๊ฐ€ ์ž ์‹œ false์ผ ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋ ‡๊ฒŒ๋˜๋ฉด ์‚ฌ์šฉ์ž๋Š” ๋กœ๊ทธ์ธ์„ ํ–ˆ์Œ์—๋„, ๋กœ๊ทธ์ธ ํ™”๋ฉด์ด ์ž ์‹œ ๋‚˜ํƒ€๋‚˜๋Š” ํ˜„์ƒ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค.

 

 

  • ๊ฐ ๊ธฐ๋Šฅ๋“ค
    ๋ชจ๋‘ authFetch๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.
 // ๊ฒ€์ƒ‰
 const handleSearch = async (keyword) => {
        try {
            // ์„œ๋ฒ„ /api/amdins๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ฌ๋ผ๊ณ  ํ•  ๋•Œ, ํ‚คํด๋ก์ด ๋ฐœ๊ธ‰ํ•ด์ค€ Bearer ํ† ํฐ์ด ํ•จ๊ป˜ ๋‚ ์•„๊ฐ„๋‹ค.
            const response = await authFetch(`/api/admins/search?keyword=${encodeURIComponent(keyword)}`);
 // ์‚ญ์ œ
 const handleDelete = async (id) => {
        if (!window.confirm("์ •๋ง ์‚ญ์ œํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?")) {
            return;
        }

        try {
            // ♦์„œ๋ฒ„ /api/amdins๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ฌ๋ผ๊ณ  ํ•  ๋•Œ, ํ‚คํด๋ก์ด ๋ฐœ๊ธ‰ํ•ด์ค€ Bearer ํ† ํฐ์ด ํ•จ๊ป˜ ๋‚ ์•„๊ฐ„๋‹ค.
            const response = await authFetch(`/api/admins/${id}`, { method: "DELETE" });

 

 

 


 

 

 


2. CustomerTable.jsx_ ๊ณ ๊ฐ์„ ๊ด€๋ฆฌํ•˜๋Š” ํŽ˜์ด์ง€

  • ์ด ํŽ˜์ด์ง€๋Š” VIP ๋“ฑ๊ธ‰ ์ด์ƒ์˜ ๊ณ ๊ฐ์„ ๊ด€๋ฆฌํ•˜๋Š” ํŽ˜์ด์ง€๋‹ค.
  • ๊ณ ๊ฐ ๊ด€๋ฆฌ ํŽ˜์ด์ง€๋Š” ์กฐํšŒ | ์ถ”๊ฐ€ | ์ˆ˜์ • | ๊ฒ€์ƒ‰ | ์‚ญ์ œ ๊ธฐ๋Šฅ์ด ์žˆ๋‹ค.
  • ๊ด€๋ฆฌ์ž๋Š” ๊ฐ–๊ณ  ์žˆ๋Š” ๊ถŒํ•œ์˜ ๊ธฐ๋Šฅ๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
    ex) A๊ด€๋ฆฌ์ž๋Š” ์กฐํšŒ | ์ถ”๊ฐ€ | ์ˆ˜์ • ๊ถŒํ•œ๋งŒ ์žˆ์œผ๋ฉด, ๊ฒ€์ƒ‰ | ์‚ญ์ œ๋Š” ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค.
  • PermissionSetting.jsx๋ž‘ ๋˜‘๊ฐ™์ด importํ•˜๊ณ  ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค.
// AuthProvider๋ฅผ ํ†ตํ•ด ํ‚คํด๋ก ์„œ๋ฒ„์—์„œ ๋ฐ›์•„์˜จ ์‚ฌ์šฉ์ž์˜ ์ •๋ณด์™€ ๊ถŒํ•œ ๋ฆฌ์ŠคํŠธ๋ฅผ auth ๋ณ€์ˆ˜์— ๋‘”๋‹ค.
const auth = useAuth();

 

  • auth ๊ถŒํ•œ ์ฒดํฌ
    // ex) ์ตœ๊ณ ๊ด€๋ฆฌ์ž || ์ฝ๊ธฐ๊ด€๋ฆฌ๊ถŒํ•œ ์ค‘ ํ•˜๋‚˜๋งŒ ๊ฐ–๊ณ  ์žˆ์œผ๋ฉด ํŒจ์Šค!
    // ๋‘˜ ์ค‘ ํ•˜๋‚˜๋งŒ true์—ฌ๋„ canRead์—” true๊ฐ€ ๋“ค์–ด๊ฐ„๋‹ค.
    const canRead = auth.superAdmin || auth.customerRead;
    const canSearch = auth.superAdmin || auth.customerSearch;
    const canAdd = auth.superAdmin || auth.customerAdd;
    const canEdit = auth.superAdmin || auth.customerEdit;
    const canDelete = auth.superAdmin || auth.customerDelete;

 

  • ๊ฐ ๊ธฐ๋Šฅ๋“ค
    ๋ชจ๋‘ authFetch๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.
  const loadCustomers = async () => {
        if (!canRead) return;
        try {
            // ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ฌ ๋•Œ(์ฝ๊ธฐ ๊ถŒํ•œ), authFetch๋กœ ํ† ํฐ์„ ๋„ฃ์–ด์„œ ๋ณด๋‚ธ๋‹ค. => ๋‹ค๋ฅธ ๋™์ž‘๋„ ๋งˆ์ฐฌ๊ฐ€์ง€
            const res = await authFetch("/api/customers");
            if (res.ok) {
                const data = await res.json();
                dispatch({ type: "INIT", payload: data });
                setPage(1);
            }
        }