๐Ÿ” ๊ถŒํ•œ ๊ธฐ๋ฐ˜ UI ์ ‘๊ทผ ์ œ์–ด (RBAC) ๊ตฌํ˜„ ๊ณผ์ •(with React + Custom Hook)

jellyjwยท2025๋…„ 4์›” 17์ผ
1
post-thumbnail

๊ถŒํ•œ ๊ธฐ๋ฐ˜ ์ ‘๊ทผ ์ œ์–ด (RBAC) ๋ž€?

์ง„ํ–‰์ค‘์ธ ํ”„๋กœ์ ํŠธ์—์„œ ์‚ฌ์šฉ์ž์—๊ฒŒ ์ฃผ์–ด์ง„ ๊ถŒํ•œ(์—ญํ• ) ์— ๋”ฐ๋ผ ๋ฉ”๋‰ด, ํ•˜์œ„ ๋ฉ”๋‰ด, ๊ทธ๋ฆฌ๊ณ  ๊ฐ ๋ฆฌ์†Œ์Šค์— ๋Œ€ํ•œ ์ ‘๊ทผ ๊ถŒํ•œ์„ ์ œ์–ดํ•˜๋Š” ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ–ˆ๋‹ค.

์ฆ‰, ์œ ์ €๋Š” ๋ณธ์ธ์˜ ๊ถŒํ•œ ๋ฒ”์œ„ ๋‚ด์—์„œ๋งŒ ๋ฉ”๋‰ด๊ฐ€ ๋ณด์—ฌ์•ผ ํ–ˆ๊ณ ,
๊ถŒํ•œ์ด ์—†๋Š” ๊ฒฝ์šฐ์—” ์•„์˜ˆ ๋ฉ”๋‰ด๋ฅผ ๋ณผ์ˆ˜ ์—†๊ณ  ํด๋ฆญ๋„ ๋ถˆ๊ฐ€๋Šฅํ•ด์•ผ ํ–ˆ๋‹ค.

์ด๋Ÿฐ ๋ฐฉ์‹์€ ์ผ๋ฐ˜์ ์œผ๋กœ RBAC(Role-Based Access Control)์ด๋ผ๊ณ  ๋ถˆ๋ฆฌ๋Š”๋ฐ,
์‚ฌ์šฉ์ž์˜ ์—ญํ• (Role)์— ๋”ฐ๋ผ ์–ด๋–ค ๋ฆฌ์†Œ์Šค(ํŽ˜์ด์ง€, ๊ธฐ๋Šฅ ๋“ฑ)์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š”์ง€๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

์ด ๊ธ€์—์„œ๋Š” ๋‚ด๊ฐ€ ์‹ค์ œ๋กœ ๊ตฌํ˜„ํ•œ ๋ฐฉ์‹๊ณผ, ๊ทธ ๊ณผ์ •์—์„œ ์ค‘์š”ํ•˜๊ฒŒ ์ƒ๊ฐํ–ˆ๋˜ ํฌ์ธํŠธ๋“ค์„ ์ •๋ฆฌํ•ด๋ณด๋ ค๊ณ  ํ•œ๋‹ค.


๐ŸŽฏ ์š”๊ตฌ์‚ฌํ•ญ

์˜ˆ๋ฅผ ๋“ค์–ด A๋ผ๋Š” ์œ ์ €์˜ ๊ถŒํ•œ์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  • B ๋ฉ”๋‰ด์— ์ ‘๊ทผํ•  ์ˆ˜ ์—†์Œ

  • C ๋ฉ”๋‰ด์˜ ํ•˜์œ„ ํƒญ ์ค‘ C-2 ์—๋งŒ ์ ‘๊ทผ ๊ถŒํ•œ์ด ์žˆ์Œ

  • C-2 ํƒญ์—์„œ๋Š” ์ƒ์„ฑ(CREATE) ๊ถŒํ•œ์ด ์—†์Œ

์ด ์ƒํ™ฉ์—์„œ UI๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋™์ž‘ํ•œ๋‹ค.

  • โœ… B ๋ฉ”๋‰ด๋Š” ๋กœ๊ทธ์ธ ์‹œ ์•„์˜ˆ ๋ณด์ด์ง€ ์•Š์•„์•ผ ํ•œ๋‹ค.

  • โœ… C ๋ฉ”๋‰ด ํด๋ฆญ ์‹œ, ์ž๋™์œผ๋กœ C-2 ํƒญ์œผ๋กœ ๋ฆฌ๋‹ค์ด๋ ‰์…˜ ๋˜์–ด์•ผ ํ•œ๋‹ค. (url์„ ์ง์ ‘ ์ž…๋ ฅ์‹œ์—๋„ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ๋™์ž‘)

  • โœ… ๋‹ค๋ฅธ ํƒญ์€ ๋ณด์—ฌ์ง€๋˜, ํด๋ฆญํ•˜๋ฉด "๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค" ์•Œ๋Ÿฟ์„ ๋„์›Œ์•ผ ํ•œ๋‹ค.

  • โœ… C-2 ํƒญ ๋‚ด์—์„œ "์ถ”๊ฐ€ํ•˜๊ธฐ" ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ์—๋„ ๊ถŒํ•œ ์ฒดํฌ๋ฅผ ํ•˜๊ณ , ์•Œ๋Ÿฟ์„ ๋„์›Œ์•ผ ํ•œ๋‹ค.


๐Ÿงฑ ๊ตฌ์กฐ ์„ค๊ณ„

๋ฐฑ์—”๋“œ๋กœ๋ถ€ํ„ฐ ๋ฐ›์€ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ๋ฅผ ๊ฐ„๋‹จํžˆ ํ‘œํ˜„ํ•ด๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.
๊ฐ ๋ฉ”๋‰ด/ํƒญ/๋ฆฌ์†Œ์Šค๋ฅผ ๊ตฌ๋ถ„ํ•  ์ˆ˜ ์žˆ๋„๋ก menuCode๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๊ถŒํ•œ ์ •๋ณด๋ฅผ ๋„˜๊ฒจ์ฃผ์—ˆ๋‹ค.

{
  "permissions": [
    {
      "menu": "C",
      "resources": ["READ"],
      "subMenus": [
        {
          "menu": "C-2",
          "resources": ["READ"]
        }
      ]
    }
  ]
}

๐Ÿ›  ๊ตฌํ˜„

์ž‘์—… ์ „ ๊ฐ€์žฅ ์ค‘์š”ํ•˜๊ฒŒ ์ƒ๊ฐํ–ˆ๋˜ ํฌ์ธํŠธ๋Š”, ๋ฌด์กฐ๊ฑด ์ƒ์œ„ ํŒŒ์ผ์—์„œ ์ œ์–ดํ•ด์•ผ ํ•œ๋‹ค๋Š” ์ ์ด์—ˆ๋‹ค.
๊ฐœ๋ณ„ ํŒŒ์ผ์—์„œ ์ ‘๊ทผ ๊ถŒํ•œ์„ ๊ด€๋ฆฌํ•  ๊ฒฝ์šฐ ์ฝ”๋“œ๊ฐ€ ํฉ์–ด์ง€๊ณ  ๋‹ค๋ฅธ ๋กœ์ง๋“ค๊ณผ ๊ฒน์ณ ์œ ์ง€๋ณด์ˆ˜์— ์–ด๋ ค์›€์ด ์žˆ์„ ์ˆ˜ ์žˆ๋‹ค.

๊ทธ๋ž˜์„œ ํŽ˜์ด์ง€๋ณ„ Layout ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜๊ณ , ํ•˜์œ„ ํƒญ๋ณ„ ๋ผ์šฐํŒ…์„ ์šฐ์„  ๋ถ„๋ฆฌํ•œ ๋’ค
ํ•ด๋‹น ๋ ˆ์ด์•„์›ƒ ํŒŒ์ผ์—์„œ ๋ฉ”๋‰ด, ํƒญ๋ณ„ ์ ‘๊ทผ ์ œ์–ด๋ฅผ ๊ด€๋ฆฌํ–ˆ๋‹ค.
๊ทธ๋ฆฌ๊ณ  ๋ชจ๋“  ํŽ˜์ด์ง€์—์„œ ๊ณตํ†ต์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก usePermission ์ด๋ผ๋Š” ์ปค์Šคํ…€ ํ›…์„ ์ƒ์„ฑํ–ˆ๋‹ค.

  • menuCode ์ƒ์ˆ˜ํ™” + ๋ผ์šฐํ„ฐ์— ๋“ฑ๋ก
    ์„œ๋ฒ„์ชฝ์—์„œ๋Š” ๊ฐ ๋ฉ”๋‰ด, ํ•˜์œ„ ํƒญ๋ณ„๋กœ ๊ณ ์œ ํ•œ '๋ฉ”๋‰ด์ฝ”๋“œ' ๋ฅผ ๋งŒ๋“ค์–ด ์ฃผ์—ˆ๋‹ค.
    ๊ฐ ๋ฉ”๋‰ด์ฝ”๋“œ๋ฅผ ์ƒ์ˆ˜๋กœ ์ƒ์„ฑํ•œ๋’ค ๋ผ์šฐํ„ฐ ๋ฆฌ์ŠคํŠธ์™€ ํƒญ ๋ฆฌ์ŠคํŠธ์— ๋ฉ”๋‰ด์ฝ”๋“œ๋ฅผ ๋“ฑ๋กํ•ด ์ฃผ์—ˆ๋‹ค.
  • ๊ถŒํ•œ ์ œ์–ด๋Š” ์ด menuCode๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํŒ๋‹จํ•œ๋‹ค.
  • ๊ณตํ†ต ์ปค์Šคํ…€ ํ›…์œผ๋กœ ๊ถŒํ•œ ์ œ์–ด

useLocation hook์„ ์ด์šฉํ•ด ํ˜„์žฌ ์œ„์น˜์— ๋”ฐ๋ฅธ ๋ฉ”๋‰ด์ฝ”๋“œ๋ฅผ ๋ฐ›์•„์˜ค๊ณ ,
๋ฉ”๋‰ด์ฝ”๋“œ๋ฅผ ์ด์šฉํ•ด ํ•ด๋‹น ๋ฉ”๋‰ด์— ๋Œ€ํ•œ ๊ถŒํ•œ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

 // ํŠน์ • ๋ฉ”๋‰ด์— ๋Œ€ํ•œ ๊ถŒํ•œ ํ™•์ธ
  const checkPermission = useCallback(
    (menuCode: string): PermissionCheckResult => {
      if (!userPermission || !userPermission.permissions) {
        return { hasPermission: false, permissionType: null };
      }

      // ์ง์ ‘ ๋ฉ”๋‰ด ์ฝ”๋“œ ๊ฒ€์ƒ‰
      const directMenuPermission = userPermission.permissions.find(
        (permission) => permission.menu === menuCode,
      );
      	....
      }
      return { hasPermission: false, permissionType: null };
    },
    [userPermission],
  );

๋งค๋ฒˆ ๋А๋ผ์ง€๋งŒ ์ด๋Ÿฐ ์ž‘์—…์€ ์ž‘์—… ์ „์— ์„ค๊ณ„ํ•  ๊ฒƒ๋„ ๋งŽ๊ณ  ๋ณต์žกํ•˜์ง€๋งŒ ๊ทธ๋งŒํผ ์žฌ๋ฐŒ๊ณ  ๋ณด๋žŒ์ฐฌ ๊ฒƒ ๊ฐ™๋‹ค.

profile
๋‚จ๋Š”๊ฑด ๊ธฐ๋ก๋ฟ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿ’ป

0๊ฐœ์˜ ๋Œ“๊ธ€