๋ณธ๋‚ด์šฉ์€ ํ•„์ž์˜ " React&Python์œผ๋กœ ์›น๊ฐœ๋ฐœ(๋งํฌ์„œ๋น„์Šค)" ์นดํ…Œ๊ณ ๋ฆฌ ๊ฐœ๋ฐœ์†Œ์Šค์— ๊ณ„์† ์ถ”๊ฐ€ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

( ์†Œ์Šค ํด๋”๋Š” ์œ„ ์นดํ…Œ๊ณ ๋ฆฌ๋ฅผ ์ž˜ ์ฝ์–ด๋ณด๊ณ ,  git ์†Œ์Šค๋ฅผ ์ฐธ๊ณ ํ•˜์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค. )

 

react-keycloak ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ useKeycloak Hook๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€ ํ˜ธ์ถœ

 

react-keycloak ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ useKeycloak Hook๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€ ํ˜ธ์ถœ

๋ณธ๋‚ด์šฉ์€ ํ•„์ž์˜ " React&Python์œผ๋กœ ์›น๊ฐœ๋ฐœ(๋งํฌ์„œ๋น„์Šค)" ์นดํ…Œ๊ณ ๋ฆฌ ๊ฐœ๋ฐœ์†Œ์Šค์— ์ถ”๊ฐ€ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ( ์†Œ์Šค ํด๋”๋Š” ์œ„ ์นดํ…Œ๊ณ ๋ฆฌ๋ฅผ ์ž˜ ์ฝ์–ด๋ณด๊ณ , git ์†Œ์Šค๋ฅผ ์ฐธ๊ณ ํ•˜์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค. ) ์ง€๋‚œํŽธ

firstvalue.tistory.com

 

์ด์ „๊ธ€์—์„œ Keycloak ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ๋กœ๊ทธ์ธ์„ ํ•˜๋„๋ก ์ฒ˜๋ฆฌํ–ˆ์—ˆ๋‹ค.

 

์ด๋ฒˆ์—๋Š” Keycloak ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋ฅผ ํ†ตํ•ด ๋กœ๊ทธ์ธํ›„ token ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์™€ Keycloak์˜ ์ €์žฅ๋œ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ํ™•์ธ ํ•ด๋ณด์ž. 

 

์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•ด์„œ ์‹ ๊ทœ ์†Œ์Šค๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.

 

์‹ ๊ทœ 1๊ฐœ์™€ ๊ธฐ์กด ์†Œ์Šค๋ฅผ ์ˆ˜์ •ํ•˜๊ณ  react์— ์‹ ๊ทœ ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•  ์˜ˆ์ •์ด๋‹ค.

1. userinfo.jsx (์‹ ๊ทœ)

2. menu.jsx (์ˆ˜์ •)

3. ์‹ ๊ทœ ํŒจํ‚ค์ง€ ์„ค์น˜ :  yarn add base-64

 

๋จผ์ € JWT ์— ๋Œ€ํ•ด์„œ ๊ฐ„๋‹จํžˆ ์„ค๋ช…ํ•˜์ž.
๋กœ๊ทธ์ธ์‹œ ํ† ํฐ ๊ด€๋ จ๋œ ์ธ์ฆ์„  ๋Œ€๋ถ€๋ถ„ ์‚ฌ์šฉํ•œ๋‹ค.
๊ทธ ์ค‘์—์„œ๋„ JWT๋Š” ์›น ํ‘œ์ค€์„ ๋”ฐ๋ฅด๊ณ  ์žˆ์œผ๋ฉฐ, JSON ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ •๋ณด๋ฅผ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.
JWT์˜ ์žฅ์  ์ค‘ ํ•˜๋‚˜๋กœ, ์›น ํ‘œ์ค€์„ ๋”ฐ๋ฅด๊ธฐ ๋•Œ๋ฌธ์— ๋Œ€๋ถ€๋ถ„์˜ ์–ธ์–ด๊ฐ€ ์ด๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

JWT๋Š” ๊ฐ๊ฐ์˜ ๊ตฌ์„ฑ์š”์†Œ๊ฐ€ ์ (.) ์œผ๋กœ ๊ตฌ๋ถ„์ด ๋˜์–ด ์žˆ์œผ๋ฉฐ , ๊ตฌ์„ฑ์š”์†Œ๋Š” HEADER(ํ—ค๋”).PAYLOAD(๋‚ด์šฉ).SIGNATURE(์„œ๋ช…) ํ˜•ํƒœ๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ๋‹ค.  

 

JWT ๋””์ฝ”๋”ฉ ํ™•์ธ ์‚ฌ์ดํŠธ : jwt.io

 

์•„๋ž˜ ์†Œ์Šค์—์„œ๋Š” ์ถ”๊ฐ€์ ์œผ๋กœ PAYLOAD ๋””์ฝ”๋”ฉ๊นŒ์ง€ ํฌํ•จํ•œ๋‹ค.

โŒ˜ userinfo.jsx ( ์œ„์น˜: linkservice/client/src/component/userinfo.jsx )

---------------------------------------------------------------------------------------------------

์•„๋ž˜ ์†Œ์Šค๋ฅผ ๋ฐ˜์˜ํ•œ๋‹ค.

import { useKeycloak } from "@react-keycloak/web";
import base64 from "base-64";
import React from "react";

const UserInfo = () => {

    const { keycloak } = useKeycloak();
    let payload, decodingInfo;
    
    const accessToken = keycloak.idToken;

    if (accessToken) {
      payload = accessToken.split('.')[1];
      decodingInfo = base64.decode(payload);
    }

  return (
    <div className="container">
      <div >

        <p>idToken ์ •๋ณด</p>
        <textarea cols="100" rows="8" value={keycloak.idToken} readOnly>
        </textarea>

        <p>token ์ •๋ณด</p>
        <textarea cols="100" rows="8" value={keycloak.token} readOnly>
        </textarea>

        <p>payload ์ •๋ณด</p>
        <textarea cols="100" rows="8" value={decodingInfo} readOnly>
        </textarea>

      </div>    
    </div>    
   )
}
export default UserInfo

 

๊ฐ„๋‹จํžˆ ์„ค๋ช…ํ•˜๋‹ค๋ฉด,

useKeycloak Hook๋ฅผ ํ†ตํ•ด keycloak ๊ฐœ๋ณ„ ๋ณ€์ˆ˜์— ๋กœ๊ทธ์ธ ์ •๋ณด๋ฅผ ํ• ๋‹นํ•˜๊ณ , accessToken๊ฐ’์„ ๋ฐ›์•„์˜จ๋‹ค.

import { useKeycloak } from "@react-keycloak/web";
//....
//....
//....
      const { keycloak } = useKeycloak();
      
// keycloak๋ฅผ ํ†ตํ•ด ๋กœ๊ทธ์ธ์ด ๋˜์—ˆ์„ ๊ฒฝ์šฐ ํ† ํฐ๊ฐ’์„ ๋ฐ›๊ฒŒ ๋œ๋‹ค.
//.....
      const accessToken = keycloak.idToken;

 

accessToken๊ฐ’์ด ์žˆ์„ ๊ฒฝ์šฐ, ์ฆ‰ ์ •์ƒ ๋กœ๊ทธ์ธ์ด ๋˜์—ˆ์„๋•Œ, JWT๊ตฌ์กฐ์ƒ splitํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด PAYLOAD๊ฐ’์„ ๊ฐ€์ ธ์™€์„œ

base64๋กœ ๋‹ค์‹œ decode๋ฅผ ํ•œ๋‹ค. 

    if (accessToken) {
      payload = accessToken.split('.')[1];
      decodingInfo = base64.decode(payload);
    }

 

 

โŒ˜ menu.jsx ( ์œ„์น˜: linkservice/client/src/component/menu.js )

---------------------------------------------------------------------------------------------------

์ด์ „ menu.jsx ์†Œ์Šค๋ฅผ ์ž„์‹œ๋กœ ์ˆ˜์ •ํ•˜์—ฌ ์ •๋ณด๋ฅผ ํ™•์ธํ•ด๋ณด์ž.

import HomeIcon from '@mui/icons-material/Home';
import SettingsIcon from '@mui/icons-material/Settings';
import { useNavigate } from 'react-router-dom';
// useKeycloak import
import { useKeycloak } from "@react-keycloak/web";
import UserInfo from './userinfo';

export default function MenuIcon(props) {
    const navigator = useNavigate()
    
    // Using Object destructuring
    const { keycloak } = useKeycloak();

    const handleIcon = (e) => {
        switch (e) {
            case 'A' : 
                // keycloak์— ๋กœ๊ทธ์ธ์ด ๋˜์–ด ์žˆ์ง€ ์•Š๋‹ค๋ฉด, keycloak ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€ ํ˜ธ์ถœ
                if (!keycloak.authenticated) {
                    alert("๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•œ ์„œ๋น„์Šค์ž…๋‹ˆ๋‹ค.")
                    // keycloak ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€ ํ˜ธ์ถœ์‹œ redirectUri๋ฅผ ๊ฐœ์ธํ™”๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค.
                    keycloak.redirectUri= window.location.href + '/manager'
                    // keycloak ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€ ํ˜ธ์ถœ
                    keycloak.login();
                }

            return navigator('/manager')
            
            case 'M' : return navigator('/')
            default : return navigator('/')
        }
    }
    return (
<>
      <div className='menu-wrap'>
        <div className='menu-box'>
          <HomeIcon fontSize='large' color="secondary"
             onClick={() => handleIcon('M')} 
          />
          <SettingsIcon fontSize='large' color="secondary"
             onClick={() => handleIcon('A')} 
          /> 
        </div>
      </div>      

//์ž„์‹œ๋กœ ๋ฐ˜์˜.. 
//๋กœ๊ทธ์ธ๋˜๋ฉด token๊ฐ’๊ณผ payload๊ฐ’์„ ๋ณด์—ฌ์ค€๋‹ค.

      { keycloak.idToken ?
          <UserInfo /> 
        : null
      }
</>
    )
}

 

menu.jsx ๋งˆ์ง€๋ง‰์— UserInfo ์ปจํฌ๋„ŒํŠธ๋ฅผ ์ž„์‹œ๋กœ ๋ฐ˜์˜ํ•œ๋‹ค.

 

      { keycloak.idToken ?
          <UserInfo /> 
        : null
      }

 

 

๊ทธ๋Ÿผ ๋กœ๊ทธ์ธ์„ ํ†ตํ•ด idToken๊ณผ Token , ๊ทธ๋ฆฌ๊ณ  payload๊ฐ’์„ ์ถœ๋ ฅํ•ด๋ณธ๋‹ค.

 

 

๋งํฌ์„œ๋น„์Šค ์‚ฌ์ดํŠธ์— ๋กœ๊ทธ์ธ์ด ๋˜์–ด token๊ฐ’๊ณผ playload์ •๋ณด๋ฅผ ๋ณผ์ˆ˜ ์žˆ๋‹ค.

๋˜ํ•œ, ๊ฐœ๋ฐœ์ž๋ชจ๋“œ console ์—์„œ๋Š” auth token๊ณผ refresh token์„ ๊ณ„์†์ ์œผ๋กœ ์ƒ์„ฑ๋จ์„ ํ™•์ธํ• ์ˆ˜ ์žˆ๋‹ค.

 

๋™์ผํ•˜๊ฒŒ  jwt.io ์‚ฌ์ดํŠธ์—์„œ๋„ ํ™•์ธํ•ด๋ณธ๋‹ค. 

๋‘๊ฐœ๋ฅผ ๋น„๊ตํ•ด๋ณด๋ฉด ๋™์ผํ•œ ๊ฒฐ๊ณผ๊ฐ’์ž„์„ ์•Œ์ˆ˜ ์žˆ๋‹ค.

 

 

๊ทธ๋Ÿผ token์ •๋ณด์—์„œ ๋กœ๊ทธ์ธ ์•„์ด๋””, ์ด๋ฆ„ ๋“ฑ ์ถœ๋ ฅํ• ๋•Œ๋Š” idTokenParsed ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ•˜๋ฉด ํ•œ๋‹ค.

 

์•„์ด๋”” ์ •๋ณด๋ฅผ ์•Œ๊ณ  ์‹ถ์„๋•Œ, 

keycloak.idTokenParsed.preferred_username

 

์œ„์™€ ๊ฐ™์€ ํ˜•ํƒœ๋กœ payload๊ฐ’์„ ์ค‘ ํ•˜๋‚˜๋ฅผ ๊ฐ€์ ธ์˜ค๋ฉด ๋œ๋‹ค.

 

 

๊ทธ๋Ÿผ ๋‹ค์ŒํŽธ์— ๊ณ„์†..................................

 

 

 

๋ณธ๋‚ด์šฉ์€ ํ•„์ž์˜ " React&Python์œผ๋กœ ์›น๊ฐœ๋ฐœ(๋งํฌ์„œ๋น„์Šค)" ์นดํ…Œ๊ณ ๋ฆฌ ๊ฐœ๋ฐœ์†Œ์Šค์— ์ถ”๊ฐ€ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

( ์†Œ์Šค ํด๋”๋Š” ์œ„ ์นดํ…Œ๊ณ ๋ฆฌ๋ฅผ ์ž˜ ์ฝ์–ด๋ณด๊ณ ,  git ์†Œ์Šค๋ฅผ ์ฐธ๊ณ ํ•˜์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค. )

 

์ง€๋‚œํŽธ์—์„œ๋Š” http://localhost:3000 ์ ‘๊ทผ์‹œ ๋ฐ”๋กœ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๊ฐ€ ๋‚˜์˜ค๋„๋ก ์ฒ˜๋ฆฌํ–ˆ๊ณ ,

์ด๋ฒˆํŽธ์—๋Š” admin ๋ฒ„ํŠผ ํด๋ฆญ ํ˜น์€ ๋กœ๊ทธ์ธ ์•„์ด์ฝ˜์„ ํด๋ฆญ ํ• ๋•Œ Keycloak ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๊ฐ€ ๋‚˜์˜ค๋„๋ก ์ˆ˜์ •ํ•ด๋ณด์ž.

 

์ด์ „์— material-icons๋ฅผ ํ†ตํ•œ menu.jsx๋ฅผ ์ œ์ž‘ํ–ˆ๋‹ค. ์•„๋ž˜ ์ด์ „๊ธ€ ์ฐธ๊ณ ๋ฐ”๋ž๋‹ˆ๋‹ค.

 

#14. material-icons ๋ฅผ ํ†ตํ•œ ๋ฉ”๋‰ดํ™”๋ฉด

 

#14. material-icons ๋ฅผ ํ†ตํ•œ ๋ฉ”๋‰ดํ™”๋ฉด

๋“ฑ๋กํ™”๋ฉด์€ ์™„๋ฃŒ๋˜์—ˆ์œผ๋‹ˆ, ์ด์   ํŽธ์ง‘ํ™”๋ฉด์„ ๊ฐœ๋ฐœํ•ด ๋ณด์ž. #13. ๋“ฑ๋กํ™”๋ฉด์—์„œ ๋‚ด์šฉ ์ €์žฅ ๋ฐ ์ด๋ฏธ์ง€ ์ €์žฅ ๊ธฐ๋Šฅ ๊ตฌํ˜„ #13. ๋“ฑ๋กํ™”๋ฉด์—์„œ ๋‚ด์šฉ ์ €์žฅ ๋ฐ ์ด๋ฏธ์ง€ ์ €์žฅ ๊ธฐ๋Šฅ ๊ตฌํ˜„ #12. React์—์„œ Drag & Drop์„

firstvalue.tistory.com

 

๊ธฐ์กด menu.jsx ์— SettingsIcon ํด๋ฆญ์‹œ Keycloak์˜ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•˜๋„๋ก ์ˆ˜์ •ํ•œ๋‹ค.

 

์ด 3๊ฐœ์˜ ์†Œ์Šค๋ฅผ ์ˆ˜์ •ํ•  ์˜ˆ์ •์ด๋‹ค.

1.  menu.jsx

2. App.js

3. index.js

โŒ˜ menu.jsx ( ์œ„์น˜: linkservice/client/src/component/menu.jsx )

---------------------------------------------------------------------------------------------------

์šฐ์„  react-keycloak/web ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๊ตฌ์„ฑ์š”์†Œ๊ฐ€ Keycloak์— Accessํ•ด์•ผํ•˜๋Š” ๊ฒฝ์šฐ 

useKeycloak Hook๋ฅผ ์‚ฌ์šฉํ•ด์•ผํ•œ๋‹ค.

import { useKeycloak } from "@react-keycloak/web";

 

useKeycloak๊ตฌ์กฐํ™”๋œ ๊ฐ์ฒด๋ฅผ Destructuring ํ•˜์—ฌ ๊ฐœ๋ณ„์ ์ธ ๋ณ€์ˆ˜์— ํ• ๋‹นํ•œ๋‹ค.

   const { keycloak } = useKeycloak();

์ด๋ ‡๊ฒŒ ํ•จ์œผ๋กœ์จ, ๋ชจ๋“  keycloak ๋ฉ”์„œ๋“œ ๋ฐ ๋ณ€์ˆ˜์— ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

handleIcon ์ด๋ฒคํŠธ์— keycloak.authenticated (์ธ์ฆ์ด๋˜์—ˆ๋Š”์ง€) ์— ๋”ฐ๋ผ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋ฅผ ํ˜ธ์ถœํ•ด๋ณด์ž.

                // keycloak์— ๋กœ๊ทธ์ธ์ด ๋˜์–ด ์žˆ์ง€ ์•Š๋‹ค๋ฉด, keycloak ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€ ํ˜ธ์ถœ
                if (!keycloak.authenticated) {
                    alert("๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•œ ์„œ๋น„์Šค์ž…๋‹ˆ๋‹ค.")
                    // keycloak ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€ ํ˜ธ์ถœ
                    keycloak.login();
                }

์œ„์™€ ๊ฐ™์ด handle ์ด๋ฒคํŠธ์— ๋”ฐ๋ผ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋ฅผ ํ˜ธ์ถœํ• ์ˆ˜์žˆ์œผ๋ฉฐ,

์ด๋ฏธ ๋กœ๊ทธ์ธ์ด ๋˜์—ˆ๋‹ค๋ฉด ๊ด€๋ฆฌ์ž ํ™”๋ฉด์œผ๋กœ ๋ฐ”๋กœ ์ด๋™์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

 

์œ„ ๋‚ด์šฉ์—์„œ ์ถ”๊ฐ€์ ์œผ๋กœ keycloak.login() ํ›„ ๊ธฐ๋ณธ redirect-uri ์ด http://localhost:3000 ์ด๋‹ค. 

์ฆ‰, ๊ด€๋ฆฌ์ž ํ™”๋ฉด์œผ๋กœ ์ด๋™์‹œ ๋กœ๊ทธ์ธ ํ›„ ๋ฐ”๋กœ ์ด๋™์ด ์•ˆ๋˜๊ณ , ์ดˆํ™”๋ฉด์ด ๋‚˜์˜จ๋‹ค๋Š” ๋œป์ด๋‹ค.

๊ทธ๋ž˜์„œ redirect-uri๋ฅผ ๊ด€๋ฆฌ์ž ํ™”๋ฉด์œผ๋กœ ์ง€์ •์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

 

                    // keycloak ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€ ํ˜ธ์ถœ์‹œ redirectUri๋ฅผ ๊ฐœ์ธํ™”๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค.
                    keycloak.redirectUri= window.location.href + '/manager'

 

์ˆ˜์ •๋œ menu.jsx


import HomeIcon from '@mui/icons-material/Home';
import SettingsIcon from '@mui/icons-material/Settings';
import { useNavigate } from 'react-router-dom';
// useKeycloak import
import { useKeycloak } from "@react-keycloak/web";

export default function MenuIcon(props) {
    const navigator = useNavigate()
    
    // Using Object destructuring
    const { keycloak } = useKeycloak();

    const handleIcon = (e) => {
        switch (e) {
            case 'A' : 
                // keycloak์— ๋กœ๊ทธ์ธ์ด ๋˜์–ด ์žˆ์ง€ ์•Š๋‹ค๋ฉด, keycloak ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€ ํ˜ธ์ถœ
                if (!keycloak.authenticated) {
                    alert("๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•œ ์„œ๋น„์Šค์ž…๋‹ˆ๋‹ค.")
                    // keycloak ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€ ํ˜ธ์ถœ์‹œ redirectUri๋ฅผ ๊ฐœ์ธํ™”๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค.
                    keycloak.redirectUri= window.location.href + '/manager'
                    // keycloak ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€ ํ˜ธ์ถœ
                    keycloak.login();
                }

            return navigator('/manager')
            
            case 'M' : return navigator('/')
            default : return navigator('/')
        }
    }
    return (
<>
      <div className='menu-wrap'>
        <div className='menu-box'>
          <HomeIcon fontSize='large' color="secondary"
             onClick={() => handleIcon('M')} 
          />
          <SettingsIcon fontSize='large' color="secondary"
             onClick={() => handleIcon('A')} 
          /> 
        </div>
      </div>      
</>
    )
}

 

โŒ˜ App.js ( ์œ„์น˜: linkservice/client/src/App.js )

---------------------------------------------------------------------------------------------------

์ด์ „ App.js ์†Œ์Šค๋‚ด์—์„œ๋Š” ReactKeycloakProvider์˜ initOptions ์„ ์ œ์™ธํ•œ๋‹ค.

 onEvent={onKeycloakEvent} ๋„ ์ œ์™ธํ•ด๋„ ๋˜๋‚˜, ๊ฐœ๋ฐœ์ž ๋ชจ๋“œ์—์„œ refressToken๊ฐ’ ํ™•์ธ ์ฐจ์›์—์„œ

๋ฐ˜์˜ํ•ด ๋‘”๋‹ค.

 

์ˆ˜์ •๋œ App.js 

import React from "react"
import { BrowserRouter, Route, Routes } from "react-router-dom"
import LinkMain from "./component/links"
import LinkCreateMain from "./component/links.create"
import ManagerHome from "./component/manager"
import MenuIcon from "./component/menu"
import LinksList from "./component/links.list"
import LinkEdit from "./component/links.edit"
import { ReactKeycloakProvider } from "@react-keycloak/web";
import keycloak, { onKeycloakEvent } from "./keycloak";
import PrivateRoute from "./PrivateRoute";
// import { onKeycloakEvent } from "./keycloak"

function App() {
  return (

    // ๊ธฐ์กด BrowserRouter ๋ฐ–์— ReactKeycloakProvider๋ฅผ ์„ ์–ธํ•œ๋‹ค. 
    // initOptions์— ์˜ํ•ด์„œ http://localhost:3000 ์œผ๋กœ ์ ‘์†์‹œ keycloak ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ์ด๋™๋œ๋‹ค.
    <ReactKeycloakProvider authClient={keycloak} onEvent={onKeycloakEvent} >
    {/* initOptions={initOptions} > ์ œ์™ธ์‹œํ‚จ๋‹ค. */}

      <BrowserRouter>    
        <Routes>
          <Route exact path="/" element={<LinkMain />} />  

          {/* PrivateRoute๋ฅผ ํ†ตํ•ด ๋กœ๊ทธ์ธ์ด ๋œ ์ƒํƒœ์—์„œ๋งŒ ํŽ˜์ด์ง€ ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•˜๊ฒŒ ์ฒ˜๋ฆฌ  */}
          <Route exact path="/create" element={<PrivateRoute> <LinkCreateMain /> </PrivateRoute>} />  
          <Route exact path="/manager" element={<PrivateRoute> <ManagerHome /> </PrivateRoute>} />  
          <Route exact path="/links/list" element={<PrivateRoute> <LinksList /> </PrivateRoute>} />  
          <Route exact path="/link/edit/:id" element={<PrivateRoute> <LinkEdit /> </PrivateRoute>} />  
        </Routes> 
        <MenuIcon />
      </BrowserRouter>

     </ReactKeycloakProvider>
  );
  
}
export default App;

 

โŒ˜ index.js ( ์œ„์น˜: linkservice/client/src/index.js )

---------------------------------------------------------------------------------------------------

๊ธฐ์กด index.js์—์„œ <React.StrictMode> ๋งŒ ์ œ์™ธํ•œ๋‹ค.

StrictMode๊ฐ€ ์žˆ์œผ๋ฉด http://localhost:3000 ์ ‘์†์‹œ ๋ฌดํ•œ๋กœ๋”ฉ์ด ๋˜๋ฏ€๋กœ, ํ•ด๋‹น StictMode๋ฅผ ๋นผ์ค€๋‹ค.

 

์ˆ˜์ •๋œ index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
// import './index.css';
import App from './App';
// import reportWebVitals from './reportWebVitals';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  // <React.StrictMode>  ์ œ์™ธ์‹œํ‚จ๋‹ค.
    <App />
  // </React.StrictMode>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
// reportWebVitals();

๊ทธ๋Ÿผ ์„œ๋ฒ„๋‹จ๊ณผ ํด๋ผ์ด์–ธํŠธ๋‹จ์„ ์‹คํ–‰ํ•ด์„œ ์ •์ƒ์ ์œผ๋กœ ๋ฐ˜์˜์ด ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•ด๋ณด์ž.

 

linkservice ํ™”๋ฉด

์ •์ƒ์ ์ธ ํŽ˜์ด์ง€๊ฐ€ ๋‚˜์˜จ๋‹ค๋ฉด ํ•˜๋‹จ SettingIcon์„ ํด๋ฆญํ•œ๋‹ค.

linkservice ํ™”๋ฉด

ํ™•์ธ์„ ๋ˆ„๋ฅด๋ฉด Keycloak์˜ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๊ฐ€ ๋‚˜์˜ค๋Š”๊ฒƒ์„ ํ™•์ธํ• ์ˆ˜ ์žˆ๋‹ค.

keycloak ๊ธฐ๋ณธ ๋กœ๊ทธ์ธ ํ™”๋ฉด

 

๋‹ค์Œ์—๋Š” ๋กœ๊ทธ์ธํ›„ token๊ฐ’ ๋ฐ ๋กœ๊ทธ์ธ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๋Š”๊ฒƒ์„ ์•Œ์•„๋ณด์ž.

 

React ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ๋Š” ํ•„์ž์˜ " React&Python์œผ๋กœ ์›น๊ฐœ๋ฐœ(๋งํฌ์„œ๋น„์Šค)" ์นดํ…Œ๊ณ ๋ฆฌ ๊ฐœ๋ฐœ์†Œ์Šค์—
์ถ”๊ฐ€ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
( ์†Œ์Šค ํด๋”๋Š” ์œ„ ์นดํ…Œ๊ณ ๋ฆฌ๋ฅผ ์ž˜ ์ฝ์–ด๋ณด๊ณ ,  git ์†Œ์Šค๋ฅผ ์ฐธ๊ณ ํ•˜์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค. )


1. ์‚ฌ์šฉ๋˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค

       โ–ถ๏ธŽ keycloak-js

       โ–ถ๏ธŽ @react-keycloak/web

 

์šฐ์„  ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์„ค์น˜ํ•œ๋‹ค.

์•„๋ž˜ ํฌ์ŠคํŒ…์—์„œ ํด๋”๊ตฌ์กฐ๋ฅผ ํ™•์ธํ•˜๊ณ ,  ํ„ฐ๋ฏธ๋‚ ์„ ์—ด์–ด ~>linserver/client ์—์„œ react ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์„ค์น˜ํ•œ๋‹ค.

 

#4. ํ”„๋กœ์ ํŠธ ํด๋” ๊ตฌ์กฐ ๋ฐ ํ™˜๊ฒฝ์„ค์ •

 

#4. ํ”„๋กœ์ ํŠธ ํด๋” ๊ตฌ์กฐ ๋ฐ ํ™˜๊ฒฝ์„ค์ •

1.1. ํ”„๋กœ์ ํŠธ ํด๋” ๊ตฌ์กฐ ๋ฐ ํ™˜๊ฒฝ์„ค์ • ํ”„๋กœ์ ํŠธ ํด๋” ๊ตฌ์กฐ๋Š” ํ”„๋ก ํŠธ์—”๋“œ์™€ ๋ฐฑ์—”๋“œ๋ฅผ ๋‚˜๋ˆ„๊ณ ์ž client / server๋กœ ๊ตฌ๋ถ„ ํ•œ๋‹ค. โŽฎํ”„๋กœ์ ํŠธ ํด๋” : linkserver/ |-------- client ( React ๊ตฌ์กฐ) |-------- server ( Python๊ตฌ

firstvalue.tistory.com

 

$ yarn add keycloak-js

$ yarn add @react-keycloak/web

2. React์— Keycloak ์„ค์ •

   โŒ˜ keyclaok.js ์œ„์น˜: linkservice/client/src/keycloak.js)

   ----------------------------------------------------------------------------------------------

keyclaok์„ค์ •์„ ์œ„ํ•ด์„œ keycloak.js ํŒŒ์ผ์„ ์ƒ์„ฑํ•œ๋‹ค.

import Keycloak from "keycloak-js";

//์ด์ „ ํฌ์ŠคํŒ…์˜ Keycloak ์‚ฌ์šฉ์ž ์•„์ด๋””์™€ ํด๋ผ์ด์–ธํŠธ ์ƒ์„ฑ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์•„๋ž˜ ์„ค์ •
const keycloak = new Keycloak({
    realm: 'aip',
    url: 'http://localhost:8080/',
    clientId: 'testClient',
});

// initOption ์ง€์ •
export const initOptions = {
    onLoad: 'login-required',
    checkLoginIframe: false,
  };

export const onKeycloakEvent = (event, error) => {
  // console.log('keycloak event ', event, error);
  switch (event) {
    case 'onAuthLogout':
      keycloak.logout();
      break;
    case 'onAuthRefreshError':
      keycloak.logout();
      break;
    case 'onAuthRefreshSuccess':
    //๊ฐœ๋ฐœ๊ณ„์—์„œ๋งŒ ์‚ฌ์šฉ, ์‹ค์ œ ์šด์˜๊ณ„์—์„œ๋Š” ์ œ์™ธ
      console.log('auth token:  ' + keycloak.token);
      console.log('refresh token:  ' + keycloak.refreshToken);
      break;
    default:
      break;
  }
};

export default keycloak;


const keycloak = new Keycloak({
    realm: 'aip',
    url: 'http://localhost:8080/',
    clientId: 'testClient',
});

ํ•ด๋‹น ์„ค์ •์€ ์•„๋ž˜ ์ด๋ฏธ์ง€์ฒ˜๋Ÿผ keycloak ์˜ Client์—์„œ ์˜ค๋ฅธ์ชฝ ์ƒ๋‹จ Action ํ•˜์œ„ ํ™”์‚ดํ‘œ๋ฅผ ํด๋ฆญํ•˜๋ฉด "Download adapter config ํ™”๋ฉด์—์„œ ํ™•์ธ์ด ๊ฐ€๋Šฅํ•˜๋‹ค. ( reaml, url, clientid ํ‚ค ๋ช…์นญ์ด ๋‹ค๋ฅด์ง€๋งŒ ๋ฌธ์ œ์—†๋‹ค. )

 

initOptions๊ณผ onKeycloakEvent๋Š” App.js์—์„œ ReactKeycloakProvider ์— ์ถ”๊ฐ€ํ•˜๊ธฐ ์œ„ํ•ด์„œ ์„ ์–ธํ•œ๋‹ค.

 

initOptions

keycloak ๋กœ๊ทธ์ธ๋ฐฉ์‹์— ๋Œ€ํ•ด์„œ ์„ค์ •์œผ๋กœ onLoad

  • login-required : ์‚ฌ์šฉ์ž๊ฐ€ Keycloak์— ๋กœ๊ทธ์ธํ•œ ๊ฒฝ์šฐ ํด๋ผ์ด์–ธํŠธ๋ฅผ ์ธ์ฆํ•˜๊ณ  ๊ทธ๋ ‡์ง€ ์•Š์€ ๊ฒฝ์šฐ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋ฅผ ํ‘œ์‹œ. 
  • check-sso : ์ด๋ฏธ ๋กœ๊ทธ์ธํ•œ ๊ฒฝ์šฐ์—๋งŒ ํด๋ผ์ด์–ธํŠธ๋ฅผ ์ธ์ฆํ•˜๊ณ , ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ๋ธŒ๋ผ์šฐ์ €๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์œผ๋กœ ๋‹ค์‹œ ๋ฆฌ๋””๋ ‰์…˜๋˜๊ณ  ์ธ์ฆ๋˜์ง€ ์•Š์€ ์ƒํƒœ๋กœ ์œ ์ง€.

์—ฌ๊ธฐ๋Š” App.js์— ReactKeycloakProvider ํ˜•ํƒœ๋กœ ์ œ๊ณตํ•˜๊ธฐ ์œ„ํ•ด์„œ login-required ๋กœ ์ฒ˜๋ฆฌํ•œ๋‹ค.

  ( ์ฆ‰, ํŠน์ •ํŽ˜์ด์ง€ ์ด๋™์‹œ ๋กœ๊ทธ์ธ์ด ์•ˆ๋˜์–ด ์žˆ์œผ๋ฉด ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋ฅผ ํ‘œ์‹œํ•˜๋„๋ก ์ฒ˜๋ฆฌ )

 

onKeycloakEvent

keycloak event๋ฅผ ์„ค์ •ํ•˜๋ฉฐ, Provider์— ์ง€์ •ํ•˜์—ฌ event์‹œ ๋งˆ๋‹ค ์ฒ˜๋ฆฌ ๋‚ด์šฉ์„ ์„ ์–ธํ•œ๋‹ค. ์œ„๋Š” default๊ฐ’์œผ๋กœ ๊ทธ๋Œ€๋กœ ๋ฐ˜์˜ํ•ด๋„ ๋ฌด๋ฐฉํ•˜๋‹ค.

 

3. PrivateRoute.js ์ƒ์„ฑ

   โŒ˜ PrivateRoute.js ์œ„์น˜: linkservice/client/src/PrivateRoute.js)

   ----------------------------------------------------------------------------------------------

Keycloak์— ๋กœ๊ทธ์ธ์ด ๋˜์–ด ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๊ณ ์ž Route๋ฅผ ์ง€์ •ํ•˜๊ธฐ ์œ„ํ•ด์„œ ์ƒ์„ฑํ•œ๋‹ค.

import { useKeycloak } from "@react-keycloak/web";

const PrivateRoute = ({ children }) => {
  const { keycloak } = useKeycloak();

  //keycloak์— ๋กœ๊ทธ์ธ์ด ๋˜์–ด ์žˆ๋Š”์ง€ ํ™•์ธํ•œ๋‹ค.
  const isLoggedIn = keycloak.authenticated;

  return isLoggedIn ? children : null;
};

export default PrivateRoute;

 

3. App.js ์— ReactKeyclaokProvider ์™€ PrivateRoute ๋ฐ˜์˜ 

   โŒ˜ App.js ์œ„์น˜: linkservice/client/src/App.js)

   ----------------------------------------------------------------------------------------------

๊ธฐ์กด App.js ๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ˆ˜์ •ํ•œ๋‹ค.

import React from "react"
import { BrowserRouter, Route, Routes } from "react-router-dom"
import LinkMain from "./component/links"
import LinkCreateMain from "./component/links.create"
import ManagerHome from "./component/manager"
import MenuIcon from "./component/menu"
import LinksList from "./component/links.list"
import LinkEdit from "./component/links.edit"

//์•„๋ž˜ ๋ถ€๋ถ„์ด ์ถ”๊ฐ€๋˜์—ˆ๋‹ค.
import { ReactKeycloakProvider } from "@react-keycloak/web";
import keycloak , { initOptions, onKeycloakEvent } from "./keycloak";
import PrivateRoute from "./PrivateRoute";

function App() {
  return (

    // ๊ธฐ์กด BrowserRouter ๋ฐ–์— ReactKeycloakProvider๋ฅผ ์„ ์–ธํ•œ๋‹ค. 
    // initOptions์— ์˜ํ•ด์„œ http://localhost:3000 ์œผ๋กœ ์ ‘์†์‹œ keycloak ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ์ด๋™๋œ๋‹ค.
    <ReactKeycloakProvider authClient={keycloak} 
    initOptions={initOptions} onEvent={onKeycloakEvent}>

      <BrowserRouter>    
        <Routes>
          <Route exact path="/" element={<LinkMain />} />  

          {/* PrivateRoute๋ฅผ ํ†ตํ•ด ๋กœ๊ทธ์ธ์ด ๋œ ์ƒํƒœ์—์„œ๋งŒ ํŽ˜์ด์ง€ ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•˜๊ฒŒ ์ฒ˜๋ฆฌ  */}
          <Route exact path="/create" element={<PrivateRoute> <LinkCreateMain /> </PrivateRoute>} />  
          <Route exact path="/manager" element={<PrivateRoute> <ManagerHome /> </PrivateRoute>} />  
          <Route exact path="/links/list" element={<PrivateRoute> <LinksList /> </PrivateRoute>} />  
          <Route exact path="/link/edit/:id" element={<PrivateRoute> <LinkEdit /> </PrivateRoute>} />  
        </Routes> 
        <MenuIcon />
      </BrowserRouter>

     </ReactKeycloakProvider>
  );
  
}
export default App;

๊ทธ๋Ÿผ http://localhost:3000 ์œผ๋กœ ์ ‘์†ํ•˜๋ฉด Keycloak์˜ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธ ํ• ์ˆ˜ ์žˆ๋‹ค.

์—ฌ๊ธฐ์—์„œ ๊ธฐ์กด์—(์ด์ „ ํฌ์ŠคํŠธ์—์„œ) ์ƒ์„ฑํ–ˆ๋˜ ์‚ฌ์šฉ์ž๋กœ ๋กœ๊ทธ์ธ์„ ํ•˜๋ฉด ๋œ๋‹ค.

 

keycloak ๊ธฐ๋ณธ ๋กœ๊ทธ์ธํ™”๋ฉด

 

๋‹ค์ŒํŽธ์—๋Š” http://localhost:3000 ์ ‘๊ทผ์‹œ ๋ฐ”๋กœ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๊ฐ€ ๋‚˜์˜ค์ง€ ์•Š๊ณ ,

admin ๋ฒ„ํŠผ ํด๋ฆญ ํ˜น์€ ๋กœ๊ทธ์ธ ์•„์ด์ฝ˜์„ ํด๋ฆญ ํ• ๋•Œ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๊ฐ€ ๋‚˜์˜ค๋„๋ก ์ˆ˜์ •ํ•ด๋ณด์ž.

 

KeyCloak ์šฉ์–ด ์ •๋ฆฌ

  • OIDC : OAuth ๊ฐ€ ๊ถŒํ•œ ๋ถ€์—ฌ๋งŒ ๋‹ค๋ฃจ๋Š” ๊ฒƒ์ด๋ผ๋ฉด OIDC ๋Š” OAuth ๋ฅผ ํฌํ•จํ•˜์—ฌ ์ธ์ฆ๊ณผ ๊ถŒํ•œ๋ถ€์—ฌ๋ฅผ ๋ชจ๋‘ ํฌํ•จํ•œ ๊ฒƒ์ด๋‹ค. SSO ์˜ ๊ตฌํ˜„์„ ์œ„ํ•œ ์ˆ˜๋‹จ์œผ๋กœ ์‚ฌ์šฉ๋œ๋‹ค.
  • Realm : ์ธ์ฆ, ๊ถŒํ•œ ๋ถ€์—ฌ๊ฐ€ ์ ์šฉ๋˜๋Š” ๋ฒ”์œ„๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ๋‹จ์œ„์ด๋‹ค. SSO ๋ฅผ ์ ์šฉํ•œ๋‹ค๊ณ  ํ–ˆ์„๋•Œ ํ•ด๋‹น SSO ๊ฐ€ ์ ์šฉ๋˜๋Š” ๋ฒ”์œ„๋Š” Realm ๋‹จ์œ„์ด๋‹ค.
  • Client : ์ธ์ฆ, ๊ถŒํ•œ ๋ถ€์—ฌ ํ–‰์œ„๋ฅผ ๋Œ€ํ–‰ํ•˜๋„๋ก ๋งก๊ธธ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋‚˜ํƒ€๋‚ด๋Š” ๋‹จ์œ„์ด๋‹ค. ๊ทธ ๋‹จ์œ„๋Š” ์›น์‚ฌ์ดํŠธ ํ˜น์€ REST API ๋ฅผ ์ œ๊ณตํ•˜๋Š” ์„œ๋น„์Šค๋„ ๋  ์ˆ˜ ์žˆ๋‹ค. ํ•˜๋‚˜์˜ Realm ์— n๊ฐœ์˜ Client ๋ฅผ ์ƒ์„ฑ, ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.
  • User : Client ์— ์ธ์ฆ์„ ์š”์ฒญํ•  ์‚ฌ์šฉ์ž๋ฅผ ๋‚˜ํƒ€๋‚ธ๋‹ค. ํ•˜๋‚˜์˜ Realm ์—๋Š” Realm ์— ์ข…์†๋œ n๊ฐœ์˜ User ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ User ๋Š” Username, Email, FirstName, LastName ์œผ๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ์ง€๋งŒ Custom User Attribute ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์‚ฌ์šฉ์ž๊ฐ€ ์›ํ•˜๋Š” ์†์„ฑ์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • Role : User ์—๊ฒŒ ๋ถ€์—ฌํ•  ๊ถŒํ•œ ๋‚ด์šฉ์„ ๋‚˜ํƒ€๋‚ธ๋‹ค. ์—ฌ๊ธฐ์—๋Š” Keycloak ์˜ REST API ๋ฅผ ์‚ฌ์šฉํ•  ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•  ์ˆ˜ ์žˆ๊ณ  ์‚ฌ์šฉ์ž๊ฐ€ ์ •์˜ํ•œ ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

 

Keycloak ์‚ฌ์šฉ์„ ์œ„ํ•œ ์„ค์ •

Keycloak ์‚ฌ์šฉ์„ ์œ„ํ•ด์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์„ค์ •์„ ํ•„์š”ํ•˜๋‹ค.

  1. Realm ์ƒ์„ฑ
  2. User ์ƒ์„ฑ
  3. Client ์ƒ์„ฑ

Realm ์€ ์ดˆ๊ธฐ์—๋Š” Master Realm ๋งŒ ์กด์žฌํ•œ๋‹ค. Master ๊ฐ€ ์•„๋‹Œ ์‹ค์ œ ์‚ฌ์šฉ์„ ์œ„ํ•œ ๋ ๋ฆ„์„ ์ƒ์„ฑํ•ด์ค€๋‹ค.

Realm ๊ธฐ์ค€ ํŒ๋‹จ์€ ๋‹จ์ผ ํšŒ์‚ฌ ๋‹จ์œ„๋ผ๊ณ  ์ƒ๊ฐ ํ•˜๋ฉด ๋ ๋“ฏ ํ•˜๋‹ค. 

   ์˜ˆ๋ฅผ ๋“ค์–ด ํฌ์Šค์ฝ” ๊ทธ๋ฃน์ธ ํฌ์Šค์ฝ”, ํฌ์Šค์ฝ”DX, ํฌ์Šค์ฝ”์ธํ„ฐ๋‚ด์…”๋„, ํฌ์Šค์ฝ”E&C ๊ฐ€ Keycloak๋ฅผ ๋„์ž…ํ•˜๋‹ค๊ณ  ๊ฐ€์ •ํ•˜๋ฉด
   Realm๋Š” ์„œ๋กœ๋‹ค๋ฅธ 4๊ฐœ๊ฐ€ ํ•„์š”ํ• ๊ฒƒ์ด๋‹ค.
   ๋‹จ, ๋งŒ์•ฝ์— ์œ„ 4๊ฐœ ๊ทธ๋ฃน์‚ฌ๊ฐ€ ๋ชจ๋‘ ์•„์ด๋”” ๋™์ผํ•œ ์•„์ด๋””๋กœ ๊ฐ ํšŒ์‚ฌ์˜ Legacy์‹œ์Šคํ…œ์„ ์‚ฌ์šฉํ•˜๊ฒŒ ํ•œ๋‹ค๋ฉด Realm๋Š”
   ํ•˜๋‚˜๋กœ ํ†ต์ผํ•ด์•ผํ•œ๋‹ค.

1. Realm ์ƒ์„ฑ

โ— ์™ผ์ชฝ์ƒ๋‹จ master ์•„๋ž˜ ํ™”์‚ดํ‘œ๋ฅผ ๋ˆŒ๋ ค Create Realm๋ฅผ ํ•ด๋ณด์ž.

โ— Realm name์„ ์ •ํ•˜๊ณ  Create๋ฅผ ๋ˆŒ๋Ÿฌ ๋‹ค์Œ์œผ๋กœ ๋„˜์–ด๊ฐ‘๋‹ˆ๋‹ค.

 

2. User ์ƒ์„ฑ

โ— Keycloak์— ๋Œ€ํ•œ ์ƒˆ ์‚ฌ์šฉ์ž๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.

     ์™ผ์ชฝ Manage -> Users๋ฅผ ํด๋ฆญํ•˜๊ณ  Create new user๋ฅผ ํด๋ฆญํ•ฉ๋‹ˆ๋‹ค.

โ— Username์„ ์ž…๋ ฅํ•˜๊ณ  Create๋ฅผ ํด๋ฆญํ•˜์—ฌ ๋‹ค์Œ ๋‹จ๊ณ„๋กœ ๋„˜์–ด๊ฐ„๋‹ค.

  - ๊ฐ ํ•ญ๋ชฉ์€ ํ•„์ˆ˜๊ฐ’์ด ์•„๋‹ˆ์ง€๋งŒ, ํ•„์š”ํ•œ ํ•ญ๋ชฉ์€ ๊ธฐ์ž…ํ•ด๋„ ๋œ๋‹ค.

  - Required user actions๋Š” ๋กœ๊ทธ์ธ ํ• ๋•Œ ํ•„์š”ํ•œ ์กฐ์น˜๋ฅผ ์„ ํƒํ• ์ˆ˜ ์žˆ์œผ๋ฉฐ, ( 'Verify email' ) email ์ฃผ์†Œ๋ฅผ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด

    ์‚ฌ์šฉ์ž์—๊ฒŒ  email๋ฅผ ๋ณด๋‚ด๊ฑฐ๋‚˜, ( 'Update profile' ) ์‚ฌ์šฉ์ž๊ฐ€ ๊ฐœ์ธ์ •๋ณด๋ฅผ ์ž…๋ ฅํ•˜๋„๋ก ์š”๊ตฌํ•˜๊ฑฐ๋‚˜,

    ('Update password') ์‚ฌ์šฉ์ž๊ฐ€ ์ƒˆ๋กœ์šด ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜๋„๋ก ์š”๊ตฌํ• ์ˆ˜ ์žˆ์œผ๋ฉฐ, ( 'Configure OTP') OTP๊ตฌ์„ฑ๋„ ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

โ— Credentials ํƒญ์—์„œ Set Password๋ฅผ ํด๋ฆญํ•ด์„œ ํŒจ์Šค์›Œ๋“œ๋ฅผ ์ž…๋ ฅํ•œ๋‹ค.

 Temporary๋ฅผ On์œผ๋กœ ํ•˜๋ฉด test์œ ์ €๊ฐ€ ์ฒ˜์Œ ๋กœ๊ทธ์ธ์‹œ ํŒจ์Šค์›Œ๋“œ๋ฅผ ๋ณ€๊ฒฝํ• ์ˆ˜ ์žˆ๋‹ค.


3. Client ์ƒ์„ฑ

Client๋Š” Application ์ธ์ฆ์„ ์œ„ํ•œ ๋‹จ์œ„๋ผ๊ณ  ๋ณด๋ฉด ๋œ๋‹ค.

 

โ— ์™ผ์ชฝ ๋ฉ”๋‰ด์—์„œ Clients์˜ Create client ๋ฅผ ํด๋ฆญํ•œ๋‹ค. 

 

โ— Client ID๋ฅผ ์ž…๋ ฅํ•œ๋‹ค.

โ— Capability config๋Š” default๋กœ ํ•˜๊ณ  Next .

 

โ— Login settings์—์„œ redirect URIs๋ฅผ ์„ค์ •ํ•œ ํ›„ Save.

 

 

Valid redirect URls ๋Š” ์‹ค์ œ ์ธ์ฆ(๋กœ๊ทธ์ธ) ์„ฑ๊ณตํ›„ ๋ฆฌ๋‹ค์ด๋ ‰์…˜ ํ• ์ˆ˜ ์žˆ๋Š” URLํŒจํ„ด
    - React๋กœ ์ƒ˜ํ”Œ ์‚ฌ์ดํŠธ๋ฅผ ๊ตฌ์ถ•ํ•  ์˜ˆ์ •์ด๋ฏ€๋กœ react ๊ธฐ๋ณธ URL์ธ http://localhost:3000 ์œผ๋กœ
       * ์™€์ผ๋“œ์นด๋“œ๋Š” react์—์„œ ๋ผ์šฐํŒ…๋œ ๋ชจ๋“  URL๋ฅผ ํ—ˆ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ ์‚ฌ์šฉ
Valid post logout redirect URls ๋Š” ๋กœ๊ทธ์•„์›ƒํ›„ ๋ฆฌ๋‹ค์ด๋ ‰์…˜ ํ•˜๋Š” URL์ด๋‹ค.
Web origins ๋Š” CORS๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๊ฐ€๋Šฅํ•˜๋ฉด ์‚ฌ์šฉ 
    - ํŠน์ • URL๋งŒ ํ—ˆ์šฉํ•  ๊ฒฝ์šฐ๋Š” ํ•ด๋‹น URL๋ฅผ ๊ธฐ์ž…ํ•˜๋ฉด ๋œ๋‹ค.

๋‹ค์Œ์—๋Š” ๊ฐ„๋‹จํ•œ react๋กœ ํ…Œ์ŠคํŠธ ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณด์ž.

 

์•ž์„  ์„ค๋ช…ํ–ˆ๋“ฏ์ด, ์˜คํ”„์†Œ์Šค SSO์ค‘ Keycloak ์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๊ณผ ์„œ๋น„์Šค์— ์ดˆ์ ์„ ๋‘” ID ๋ฐ ์ ‘๊ทผ ๊ด€๋ฆฌ(Access Management)์— ํ†ตํ•ฉ ์ธ์ฆ(SSO)์„ ํ—ˆ์šฉํ•˜๋Š” ์˜คํ”ˆ ์†Œ์Šค ์†Œํ”„ํŠธ์›จ์–ด๋กœ Kubernetes ๋˜๋Š” MSA ํ™˜๊ฒฝ์— ์ตœ์ ํ™” ๋œ ์†”๋ฃจ์…˜ ์ž…๋‹ˆ๋‹ค. ์‰ฝ๊ฒŒ ๋งํ•˜๋ฉด ์ธ์ฆ(Authentification)๊ณผ ์ธ๊ฐ€(Authorization)๋ฅผ ์‰ฝ๊ฒŒ ํ•ด์ฃผ๊ณ  SSO(Single-Sign-On)๋ฅผ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•ด์ฃผ๋Š” ๊ฒƒ์ด๋‹ค.

 

์˜ค๋Š˜์€ ์šฐ๋ถ„ํˆฌ(Ubuntu) ํ™˜๊ฒฝ์—์„œ Keycloak๋ฅผ ์„ค์น˜ ํ•ด ๋ณด์ž.

 

โ€ป ์šฐ๋ถ„ํˆฌ ๋ฒ„์ „์€ Ubuntu 22.04.1 LTS ์ž…๋‹ˆ๋‹ค.

 

์‚ฌ ์ „ ์ค€ ๋น„

1. JAVA ์„ค์น˜

sudo apt-get install openjdk-11-jdk
 ์ตœ์‹  Keycloak๋Š” OpenJDK 11 ์ด์ƒ ์„ค์น˜๊ฐ€ ํ•„์š”ํ•˜๋‹ค.

 

2. KeyCloak ๋‹ค์šด๋กœ๋“œ

๋‹ค์šด๋กœ๋“œ keycloak-21.1.1.tar.gz

์ตœ์‹ ๋ฒ„์ „ keycloak ๊ณต์‹ ํ™ˆํŽ˜์ด์ง€์—์„œ ํ™•์ธ ๋ฐ”๋ž๋‹ˆ๋‹ค.  
 

downloads - Keycloak

Downloads 21.1.1 For a list of community maintained extensions check out the Extensions page. Server Quickstarts Client Adapters WildFly [DEPRECATED] <= 23 ZIP (sha1) TAR.GZ (sha1) JBoss EAP [DEPRECATED] 7 ZIP (sha1) TAR.GZ (sha1) JavaScript Node.js [DEPRE

www.keycloak.org

3. ๋‹ค์šด๋กœ๋“œ ๋œ ํŒŒ์ผ ์••์ถ• ํ’€๊ธฐ

$ tar -zxvf keycloak-21.1.1.tar.gz

 

4. ๊ธฐ๋ณธ ์‹คํ–‰

  • keycloak ๋””๋ ‰ํ„ฐ๋ฆฌ๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค.
$cd keycloak-21.1.1/
  • Keycloak๋ฅผ ์‹œ์ž‘ํ•˜๊ธฐ ์ „์— ์ดˆ๊ธฐ ๊ด€๋ฆฌ์ž ๊ณ„์ •์„ ์ƒ์„ฑ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • Keycloak๋Š” default admin ์œ ์ €๊ฐ€ ์—†์œผ๋ฏ€๋กœ ํ™˜๊ฒฝ์„ ์…‹ํŒ…ํ•˜์—ฌ ์‹คํ–‰ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
$ export KEYCLOAK_ADMIN=admin
$ export KEYCLOAK_ADMIN_PASSWORD=admin
  • keycloak์„ ์‹คํ–‰ ํ•ฉ๋‹ˆ๋‹ค.
$ cd bin
$ ./kc.sh start-dev

 

  • http://localhost:8080 ์ ‘์†

keycloak ํ™”๋ฉด

 

5. Administration Console ์ ‘์†

์œ„ ํŽ˜์ด์ง€๊ฐ€ ๋‚˜์˜ค๋ฉด Adminnistration Console ํƒญ์„ ํด๋ฆญํ•ด ์ค๋‹ˆ๋‹ค.

Keycloak ๋กœ๊ทธ์ธ ํ™”๋ฉด

์ดˆ๊ธฐ์— exportํ•œ ๊ด€๋ฆฌ์ž ๊ณ„์ •๊ณผ ํŒจ์Šค์›Œ๋“œ๋ฅผ ์ž…๋ ฅํ•ฉ๋‹ˆ๋‹ค. ( admin / admin )

 

๋กœ๊ทธ์ธ์„ ํ•˜๊ฒŒ ๋˜๋ฉด ์•„๋ž˜์™€ ๊ฐ™์€ KeyCloak ๊ธฐ๋ณธ ๋Œ€์‹œ๋ณด๋“œ๊ฐ€ ํ‘œ์‹œ๋œ๋‹ค.

 

๋‹ค์ŒํŽธ์—๋Š” realm, client, user ์ƒ์„ฑ๋“ฑ์„ ์•Œ์•„๋ณด์ž.

๋Œ€๊ทœ๋ชจ ์กฐ์ง(๋Œ€๊ธฐ์—… ํ˜น์€ ์ค‘์†Œ๊ธฐ์—…)์˜ ์‚ฌ์šฉ์ž๋Š” SSO(Single Sign-On) ์†Œํ”„ํŠธ์›จ์–ด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์›ํ™œํ•˜๊ฒŒ ๋ชจ๋“  ์ž์›(์‹œ์Šคํ…œ) ์— ์ ‘๊ทผํ•ฉ๋‹ˆ๋‹ค.SSO ์†Œํ”„ํŠธ์›จ์–ด๋ฅผ ์‚ฌ์šฉํ•œ ID ๋ฐ ์•ก์„ธ์Šค ๊ด€๋ฆฌ๋Š” ์ผ์ƒ์ ์ธ ๋น„์ฆˆ๋‹ˆ์Šค ์šด์˜์˜ ํ•„์ˆ˜์ ์ธ ๋ถ€๋ถ„์ด ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ธ๊ธฐ ์žˆ๋Š” ์˜คํ”ˆ ์†Œ์Šค ์‹ฑ๊ธ€ ์‚ฌ์ธ์˜จ(SSO) ์†Œํ”„ํŠธ์›จ์–ด ์ƒ์œ„ 3๊ฐœ๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

  • IdentityServer
  • KeyCloak
  • WSO2

IdentityServer

IdentityServer๋Š” ์˜คํ”ˆ ์†Œ์Šค ๋ฌด๋ฃŒ ์‹ฑ๊ธ€ ์‚ฌ์ธ์˜จ ์†Œํ”„ํŠธ์›จ์–ด์ž…๋‹ˆ๋‹ค. OpenID Connect ๋ฐ OAuth 2๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๋Š” ๊ต์ฐจ ํ”Œ๋žซํผ ํ”„๋ ˆ์ž„์›Œํฌ์ž…๋‹ˆ๋‹ค. ๋˜ํ•œ ์ด ์˜คํ”ˆ ์†Œ์Šค ์†Œํ”„ํŠธ์›จ์–ด๋Š” ์—ฌ๋Ÿฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ๋Œ€ํ•œ ์ค‘์•™ ์ธ์ฆ ๋ฐ ๊ถŒํ•œ ๋ถ€์—ฌ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. federated identities, ๋‹ค์ค‘ ํ๋ฆ„ ๋ฐ API ์ธ์ฆ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ ์ด ์ž์ฒด ํ˜ธ์ŠคํŒ… ์†Œํ”„ํŠธ์›จ์–ด๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์‚ฌ์šฉ์ž๊ฐ€ ์—ฌ๋Ÿฌ ์•ฑ์—์„œ ๋‹จ์ผ ์‚ฌ์šฉ์ž ์ด๋ฆ„/๋น„๋ฐ€๋ฒˆํ˜ธ ์„ธํŠธ๋กœ ๋กœ๊ทธ์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

IdentityServer๋Š” C#์œผ๋กœ ์ž‘์„ฑ๋˜์—ˆ์œผ๋ฉฐ ๋ฐฐํฌ ๋ฐ ๊ฐœ๋ฐœ์— ๊ด€ํ•œ ๋ฌธ์„œ์™€ ํ•จ๊ป˜ Github์—์„œ ๋ชจ๋“  ์†Œ์Šค ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

๋” ์•Œ์•„๋ณด๊ธฐ

 

KeyCloak

KeyCloak์€ OpenID Connect, OAuth2.0 ๋ฐ SAML2.0์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๋Š” ๋˜ ๋‹ค๋ฅธ ๋ฌด๋ฃŒ ์†Œํ”„ํŠธ์›จ์–ด์ž…๋‹ˆ๋‹ค. 

์›น ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ ๋ฐ ์›น ์„œ๋น„์Šค์—์„œ SSO ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๋ฌด์—‡๋ณด๋‹ค๋„ ์ด ์˜คํ”ˆ ์†Œ์Šค ์†Œํ”„ํŠธ์›จ๋Š” LDAP ๋ฐ Active Directory์™€์˜ ํ†ตํ•ฉ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

์‚ฌ์šฉ์ž๊ฐ€ ์—ญํ• , ๊ถŒํ•œ ๋ฐ ์„ธ์…˜์„ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๋…ผ๋ฆฌ์  ์‚ฌ์šฉ์ž ์ธํ„ฐํŽ˜์ด์Šค๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

๋˜ํ•œ Java, JavaScript ๋ฐ C#๊ณผ ๊ฐ™์€ ๋‹ค์–‘ํ•œ ์–ธ์–ด์— ๋Œ€ํ•œ ํด๋ผ์ด์–ธํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

KeyCloak์€ ์ฃผ๋กœ Java๋กœ ์ž‘์„ฑ๋˜๋ฉฐ JavaScript๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค..

์†Œ์Šค ์ฝ”๋“œ๋Š” Github์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

๋” ์•Œ์•„๋ณด๊ธฐ

 

WSO2

WSO2๋Š” ๋„๋ฆฌ ์‚ฌ์šฉ๋˜๋Š” ๋˜ ๋‹ค๋ฅธ ์˜คํ”ˆ ์†Œ์Šค ID ๋ฐ ์•ก์„ธ์Šค ๊ด€๋ฆฌ ์‹œ์Šคํ…œ์ž…๋‹ˆ๋‹ค. ๊ฑฐ์˜ ๋ชจ๋“  ๋Œ€์ค‘์ ์ธ ์‹ ์› ํ‘œ์ค€์„ ์ง€์›ํ•˜์—ฌ ์ธ์ฆ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๊ณผ์˜ ํ†ตํ•ฉ์„ ์œ„ํ•ด API ์—”๋“œ ํŒŒ์ธํŠธ๋ฅผ ๊ณต๊ฐœํ–ˆ์Šต๋‹ˆ๋‹ค. WSO2๋Š” ์‚ฌ์šฉ์ž ์ •์˜๊ฐ€ ๊ฐ€๋Šฅํ•œ ์‚ฌ์šฉ์ž ์นœํ™”์ ์ธ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ ์ด ์˜คํ”ˆ ์†Œ์Šค ์†Œํ”„ํŠธ์›จ์–ด๋Š” 2๋‹จ๊ณ„ ์ธ์ฆ๋„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ฃผ๋กœ Java๋กœ ์ž‘์„ฑ๋˜์—ˆ์œผ๋ฉฐ ๋ชจ๋“  ์†Œ์Šค ์ฝ”๋“œ๋Š” ๊ฐœ๋ฐœ ๋ฐ ๋ฐฐํฌ์— ๊ด€ํ•œ ๋ฌธ์„œ์™€ ํ•จ๊ป˜ Github์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

๋” ์•Œ์•„๋ณด๊ธฐ

 

์–ธ๊ธ‰๋œ ์˜คํ”ˆ ์†Œ์Šค SSO ์†Œํ”„ํŠธ์›จ์–ด๋Š” ์—”ํ„ฐํ”„๋ผ์ด์ฆˆ ์ˆ˜์ค€์—์„œ ๋„๋ฆฌ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. 

๋น„์ฆˆ๋‹ˆ์Šค๋ฅผ ์œ„ํ•œ ์ตœ๊ณ ์˜ ์‹ฑ๊ธ€ ์‚ฌ์ธ์˜จ ์†Œํ”„ํŠธ์›จ์–ด๋ฅผ ์„ ํƒํ•˜๋Š” ์ค‘์ด๋ผ๋ฉด ์œ„ ๋‚ด์šฉ์„ ์ฐธ๊ณ ํ•˜์—ฌ ํ™•์ธํ•˜์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.

 

์ €๋Š” ๊ฐ€์žฅ ์‚ฌ์šฉํ•˜๊ธฐ ์‰ฌ์šด KeyCloak์„ ํ†ตํ•œ ์ธ์ฆ ๋ฐฉ์‹์— ๋Œ€ํ•ด์„œ ์•ž์œผ๋กœ ๊ฒŒ์‹œ๊ธ€์„ ์ž‘์„ฑํ•  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค.

 

+ Recent posts