๐ŸŒฑ Infra/KeyCloak

[keycloak ๋ง›๋ณด๊ธฐ #5] Keycloak์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํ†ตํ•ฉ

mini_world 2024. 12. 17. 22:29
๋ชฉ์ฐจ ์ ‘๊ธฐ

์ฐธ๊ณ ์ž๋ฃŒ

 


ํ†ตํ•ฉ ๋ฐฉ์‹ ์„ค๋ช…

 

Keycloak๊ณผ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ํ†ตํ•ฉํ•  ๋•Œ,  Embedded์™€ Proxied ๋ฐฉ์‹์ด ์žˆ๋‹ค.
๊ฐ ๋ฐฉ๋ฒ•์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๊ตฌ์กฐ์™€ ๋ณด์•ˆ ์š”๊ตฌ ์‚ฌํ•ญ์— ๋”ฐ๋ผ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋‹ค.

๊ตฌ๋ถ„ Embedded Proxied
์„ค๋ช… ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฝ”๋“œ ๋‚ด์—์„œ Keycloak ์–ด๋Œ‘ํ„ฐ๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•˜์—ฌ ์ธ์ฆ ๋ฐ ๊ถŒํ•œ ๋ถ€์—ฌ๋ฅผ ์ฒ˜๋ฆฌํ•œ๋‹ค. ์—ญ๋ฐฉํ–ฅ ํ”„๋ก์‹œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ  Keycloak๊ณผ์˜ ํ†ต์‹ ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ์—ญ๋ฐฉํ–ฅ ํ”„๋ก์‹œ๋ฅผ ์„ค์ •ํ•˜์—ฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์•ž๋‹จ์—์„œ ์ธ์ฆ์„ ์ฒ˜๋ฆฌํ•œ๋‹ค.
์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฝ”๋“œ์™€ ๋…๋ฆฝ์ ์œผ๋กœ Keycloak๊ณผ์˜ ํ†ตํ•ฉ์„ ๊ด€๋ฆฌํ•œ๋‹ค.
์žฅ์  ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฝ”๋“œ์—์„œ ์ธ์ฆ ํ๋ฆ„์„ ์ง์ ‘ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋‹ค.
๋‹ค์–‘ํ•œ ์ธ์ฆ ๋ฐ ๊ถŒํ•œ ๋ถ€์—ฌ ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.
์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฝ”๋“œ์—์„œ ์ธ์ฆ ๋กœ์ง์„ ์ œ๊ฑฐํ•˜์—ฌ ์ฝ”๋“œ๊ฐ€ ๋‹จ์ˆœํ•ด์ง„๋‹ค.
์ธ์ฆ ๋ฐ ๊ถŒํ•œ ๋ถ€์—ฌ ๋กœ์ง์ด ํ”„๋ก์‹œ์—์„œ ์ฒ˜๋ฆฌ๋˜๋ฏ€๋กœ ๋ณด์•ˆ์ด ๊ฐ•ํ™”๋œ๋‹ค.
๋‹จ์  ๋ณต์žก์„ฑ ์ฆ๊ฐ€: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฝ”๋“œ์— ์ธ์ฆ ๋กœ์ง์ด ํฌํ•จ๋˜์–ด ๋ณต์žก์„ฑ์ด ์ฆ๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค.
๋ณด์•ˆ ์œ„ํ—˜: ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ ์ง์ ‘ ํ† ํฐ์„ ๊ด€๋ฆฌํ•  ๊ฒฝ์šฐ ๋ณด์•ˆ ์œ„ํ—˜์ด ์ฆ๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค.
ํ”„๋ก์‹œ ์„ค์ •์ด ๋ณต์žกํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ถ”๊ฐ€์ ์ธ ์ธํ”„๋ผ๊ฐ€ ํ•„์š”ํ•  ์ˆ˜ ์žˆ๋‹ค.
ํ”„๋ก์‹œ์—์„œ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋Šฅ์— ์˜์กดํ•˜๊ฒŒ ๋˜์–ด, ํŠน์ • ์ธ์ฆ ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐ ์ œํ•œ์ด ์žˆ์„ ์ˆ˜ ์žˆ๋‹ค.

๋‘ ๋ฐฉ์‹ ๋ชจ๋‘ ์ƒํ™ฉ์— ๋”ฐ๋ผ ์„ ํƒํ•  ์ˆ˜ ์žˆ๊ณ , ๋•Œ๋กœ๋Š” ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

Certified OpenID Connect Implementations ์—์„œ ์ธ์ฆ๋œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š”๊ฒƒ์ด ์ข‹๋‹ค.

 


Javascript ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํ†ตํ•ฉ

 

์‹ค์Šต ์ง„ํ–‰

์‹ค์Šต์„ ์œ„ํ•ด Keycloak์„ ์ค€๋น„ํ•œ๋‹ค.

docker run -p 8080:8080 \
          -e KEYCLOAK_ADMIN=admin \
          -e KEYCLOAK_ADMIN_PASSWORD=admin \
          quay.io/keycloak/keycloak \
          start-dev

http://localhost:8080/์œผ๋กœ ์ ‘์†ํ•˜์—ฌ ์ƒˆ๋กœ์šด realm์„ ์ƒ์„ฑํ–ˆ๋‹ค. (์˜ต์…˜/์ƒ๋žต๊ฐ€๋Šฅ)

 

์ƒˆ๋กœ์šด client๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.

  • client id: javascript
  • root URL: http://localhost:8000
  • valid redirect URIs: http://localhost:8000/*

์ƒˆ๋กœ์šด ์‚ฌ์šฉ์ž๋„ ์ƒ์„ฑํ•œ๋‹ค.

 

์ด์ œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ค€๋น„ํ•œ๋‹ค.

# ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ๋‹ค์šด๋กœ๋“œ
git clone https://github.com/PacktPublishing/Keycloak---Identity-and-Access-Management-for-Modern-Applications-2nd-Edition.git
cd ch7/keycloak-js-adapter

realm๊ณผ client๋ฅผ ์ƒ์„ฑํ–ˆ์œผ๋‹ˆ, ์ฝ”๋“œ๋„ ์ˆ˜์ •ํ•ด์ฃผ์ž.

# ch7/keycloak-js-adapter/public/keycloak.json
{
  "realm": "myservice",
  "auth-server-url": "http://localhost:8080",
  "ssl-required": "external",
  "resource": "javascript",
  "public-client": true
}
# ch7/keycloak-js-adapter/app.js
import express from 'express';
import stringReplace from 'string-replace-middleware';

const app = express();
const port = 8000;

app.use(stringReplace({
  KC_URL: process.env.KC_URL || "http://localhost:8080"
}));

app.use('/', express.static('public'));

app.listen(port, () => {
  console.log(`Listening on port ${port}.`);
});

keycloak์€ ๋กœ์ปฌ์˜ 8080ํฌํŠธ์—์„œ ์šด์˜๋˜๋ฉฐ, ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์€ 8000ํฌํŠธ๋กœ ์˜ฌ๋ผ์˜ค๊ฒŒ ๋œ๋‹ค.
์ด์ œ ์‹คํ–‰ํ•ด๋ณด์ž.

# ๊ฒฝ๋กœ ์ž˜ ๋“ค์–ด์™€์žˆ๋Š”์ง€ ํ•œ๋ฒˆ ๋” ํ™•์ธ
cd Keycloak---Identity-and-Access-Management-for-Modern-Applications-2nd-Edition/ch7/keycloak-js-adapter
# ์‹œ์ž‘
npm install
npm start

๋ธŒ๋ผ์šฐ์ €์—์„œ http://localhost:8000 ์ ‘์†ํ•ด๋ณด์ž.

์ ‘์†ํ•˜๋ฉด ๋ฐ”๋กœ keycloak์œผ๋กœ ์—ฐ๊ฒฐ๋˜๋ฉฐ, ๋กœ๊ทธ์ธ ํ•˜๋ฉด ํŽ˜์ด์ง€์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.

์„ค๋ช…

์ฝ”๋“œ๋ฅผ ํ™•์ธํ•ด๋ณด์ž.

javascript๋ฅผ 8000๋ฒˆ ํฌํŠธ๋กœ ๋„์šฐ๊ธฐ ์œ„ํ•ด ์‹คํ–‰ํ•˜๋Š” ์ด app.js์ฝ”๋“œ์—์„œ๋Š”
string-replace-middleware๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ •์  ํŒŒ์ผ์„ ์ œ๊ณตํ•  ๋•Œ, ํŒŒ์ผ ๋‚ด์˜ KC_URL ๋ณ€์ˆ˜์— Keycloak ์ฃผ์†Œ๋ฅผ ๋‹ด๋Š”๋‹ค.

// ch7/keycloak-js-adapter/app.js
import express from 'express';
import stringReplace from 'string-replace-middleware';

const app = express();
const port = 8000;

// ํ™˜๊ฒฝ ๋ณ€์ˆ˜ KC_URL์„ ์‚ฌ์šฉํ•˜์—ฌ Keycloak ์„œ๋ฒ„์˜ URL์„ ์„ค์ •ํ•œ๋‹ค.
app.use(stringReplace({
  KC_URL: process.env.KC_URL || "http://localhost:8080"
}));

app.use('/', express.static('public'));

app.listen(port, () => {
  console.log(`Listening on port ${port}.`);
});

 

keycloak.json Keycloak ํด๋ผ์ด์–ธํŠธ ์„ค์ •์„ ํฌํ•จํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, Keycloak ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์ดˆ๊ธฐํ™”๋  ๋•Œ ํ•ด๋‹น ์„ค์ •์„ ์‚ฌ์šฉํ•œ๋‹ค.
(Keycloak JavaScript ์–ด๋Œ‘ํ„ฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์ž๋™์œผ๋กœ ๋กœ๋“œํ•จ)

์ž‘๋™ ๋ฐฉ์‹์€ ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

  1. Keycloak ์ดˆ๊ธฐํ™”: index.html์—์„œ Keycloak() ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๊ณ  init ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ, ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ keycloak.json ํŒŒ์ผ์„ ์ฐพ๋Š”๋‹ค.
  2. ์„ค์ • ๋กœ๋“œ: keycloak.json ํŒŒ์ผ์ด public ๋””๋ ‰ํ† ๋ฆฌ์— ์œ„์น˜ํ•ด ์žˆ์œผ๋ฉด, Keycloak ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์ด ํŒŒ์ผ์„ ์ž๋™์œผ๋กœ ๋กœ๋“œํ•˜์—ฌ ํ•„์š”ํ•œ ์„ค์ •์„ ์ ์šฉํ•œ๋‹ค.
# ch7/keycloak-js-adapter/public/keycloak.json
{
  "realm": "myservice",
  "auth-server-url": "http://localhost:8080",
  "ssl-required": "external",
  "resource": "javascript",
  "public-client": true
}

 

index.html์€ Keycloak์„ ์‚ฌ์šฉํ•˜์—ฌ ์‚ฌ์šฉ์ž ์ธ์ฆ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ์›น ํŽ˜์ด์ง€๋‹ค. ์ฝ”๋“œ๋Š” ๊ธธ์–ด์„œ ์„ค๋ช…๋งŒ ์ถ”๊ฐ€ํ–ˆ๋‹ค.

<script src="KC_URL/js/keycloak.js"></script>

์ด ๋ถ€๋ถ„์—์„œ KC_URL์€ app.js์—์„œ ์„ค์ •๋œ Keycloak ์„œ๋ฒ„์˜ URL๋กœ ๋Œ€์ฒด๋œ๋‹ค.
์ด ๊ณผ์ •์€ Keycloak ์ดˆ๊ธฐํ™”์˜ ์ผ๋ถ€๋กœ, Keycloak() ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๊ณ  init ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ Keycloak์„ ์ดˆ๊ธฐํ™”ํ•œ๋‹ค.
์ด๋•Œ keycloak.json ํŒŒ์ผ์ด ์ž๋™์œผ๋กœ ๋กœ๋“œ๋˜๋ฉฐ, Keycloak JavaScript ์–ด๋Œ‘ํ„ฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ๋กœ๋“œ๋˜์–ด ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ Keycloak๊ณผ์˜ ํ†ต์‹ ์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.

<!-- ์ƒ๋žต -->
      document.getElementById("logout").addEventListener("click", () => {
        keycloak.logout();
      });

      document.getElementById("showIdToken").addEventListener("click", () => {
        output(keycloak.idTokenParsed);
      });

      document
        .getElementById("showAccessToken")
        .addEventListener("click", () => {
          output(keycloak.tokenParsed);
        });

      document
        .getElementById("refreshToken")
        .addEventListener("click", async () => {
          await keycloak.updateToken(-1);
          output(keycloak.idTokenParsed);
          showProfile();
        });

      document
        .getElementById("showMyAccount")
        .addEventListener("click", async () => {
          await keycloak.accountManagement()
        });
<!-- ์ƒ๋žต -->

๋˜ํ•œ ์ฝ”๋“œ๋‚ด ๋ฒ„ํŠผ์„ ํ†ตํ•ด ํด๋ฆญ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ Keycloak์˜ ๊ธฐ๋Šฅ์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ–ˆ๋‹ค.

  • ๋กœ๊ทธ์•„์›ƒ: keycloak.logout()์„ ํ˜ธ์ถœํ•˜์—ฌ ์‚ฌ์šฉ์ž๋ฅผ ๋กœ๊ทธ์•„์›ƒํ•œ๋‹ค.
  • ํ† ํฐ ํ‘œ์‹œ: keycloak.idTokenParsed)์„ ํ†ตํ•ด ํ† ํฐ๊ณผ ์•ก์„ธ์Šค ํ† ํฐ์„ JSON ํ˜•์‹์œผ๋กœ ์ถœ๋ ฅํ•œ๋‹ค.
  • ํ† ํฐ ๊ฐฑ์‹ : keycloak.updateToken()์„ ํ˜ธ์ถœํ•˜์—ฌ ํ† ํฐ์„ ๊ฐฑ์‹ ํ•œ๋‹ค.
  • ๊ณ„์ • ๊ด€๋ฆฌ: keycloak.accountManagement()๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์‚ฌ์šฉ์ž์˜ ๊ณ„์ • ๊ด€๋ฆฌ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•œ๋‹ค.

 


Nodejs ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํ†ตํ•ฉ (frontend)

 

์‹ค์Šต ์ง„ํ–‰

์‹ค์Šต์„ ์ง„ํ–‰ํ•ด๋ณด์ž. 
์œ„ ๋‹จ๊ณ„์—์„œ keycloak์„ ๋„์ปค๋กœ ๋„์šฐ๊ณ  realm์„ ์ƒ์„ฑํ–ˆ์œผ๋‹ˆ ํ•ด๋‹น ๋‹จ๊ณ„๋Š” ๊ฑด๋„ˆ๋›ฐ๊ณ  ๋ฐ”๋กœ client๋ถ€ํ„ฐ ์ƒ์„ฑํ•œ๋‹ค.

  • client id: nodejs
  • root URL: http://localhost:8000
  • client authentication: on

๋ณด์•ˆ client์ด๋ฏ€๋กœ, Credentials ํƒญ์—์„œ Secret์„ ๋ณต์‚ฌํ•ด๋‘”๋‹ค.

 

์ด์ œ ์ฝ”๋“œ๋ฅผ ์ค€๋น„ํ•œ๋‹ค.

# ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ๋‹ค์šด๋กœ๋“œ
git clone https://github.com/PacktPublishing/Keycloak---Identity-and-Access-Management-for-Modern-Applications-2nd-Edition.git
cd ch7/nodejs/frontend

keycloak ์„ค์ •์— ๋งž๊ฒŒ ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•œ๋‹ค.
์œ„์—์„œ ๋ณต์ œํ•œ client secret์œผ๋กœ ๊ฐ’์„ ๋ณ€๊ฒฝํ•˜๊ณ , 8000ํฌํŠธ์—์„œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์‹œ์ž‘๋  ์ˆ˜ ์žˆ๋„๋ก ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•œ๋‹ค.

// ch7/nodejs/frontend/app.js
var express = require('express');
var session = require('express-session');
var Keycloak = require('keycloak-connect');
var cors = require('cors');

var app = express();

app.use(cors());

var memoryStore = new session.MemoryStore();

app.use(session({
    secret: 'io9bBSukiwEyUekE62g88aeI4CKYUBTi', // ์œ„์—์„œ ๋ณต์‚ฌํ•œ client secret ์ž…๋ ฅ
    resave: false,
    saveUninitialized: true,
    store: memoryStore
}));

var keycloak = new Keycloak({ store: memoryStore });

app.use(keycloak.middleware());

app.get('/', keycloak.protect(), function (req, res) {
    res.setHeader('content-type', 'text/plain');
    res.send('Welcome!');
});

app.listen(8000, function () {         // ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํฌํŠธ 8000์œผ๋กœ ์ˆ˜์ •
    console.log('Started at port 8000'); 
});
# ch7/nodejs/frontend/keycloak.json
{
  "realm": "myservice", 
  "auth-server-url": "${env.KC_URL:http://localhost:8080}",
  "resource": "nodejs",
  "credentials" : {
    "secret" : "io9bBSukiwEyUekE62g88aeI4CKYUBTi"
  }
}

์ด์ œ ์‹คํ–‰ํ•ด๋ณด์ž

# ๊ฒฝ๋กœ ์ž˜ ๋“ค์–ด์™€์žˆ๋Š”์ง€ ํ•œ๋ฒˆ ๋” ํ™•์ธ
cd Keycloak---Identity-and-Access-Management-for-Modern-Applications-2nd-Edition/ch7/nodejs/frontend
# ์‹œ์ž‘
npm install
npm start

๋กœ์ปฌ ๋ธŒ๋ผ์šฐ์ €์—์„œ http://localhost:8000์„ ์ ‘์†ํ•˜๋ฉด ์•„๋ž˜์™€ ๊ฐ™์€ ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์˜จ๋‹ค.

์„ค๋ช…

๋จผ์ € keycloak.json ์€ Keycloak ํด๋ผ์ด์–ธํŠธ ์„ค์ •์„ ํฌํ•จํ•œ๋‹ค.

์œ„ javascript ์‹ค์Šต์—์„œ๋„ keycloak.jsonํŒŒ์ผ์ด ์‚ฌ์šฉ๋˜์—ˆ๋Š”๋ฐ ์ด๋Š” javascriptํ™˜๊ฒฝ์—์„œ ์ฃผ๋กœ ์‚ฌ์šฉ๋˜๋Š” ํ‘œ์ค€์œผ๋กœ, 
๋‹ค๋ฅธ ์–ธ์–ด์—์„œ๋Š” XML๋“ฑ์˜ ํ˜•์‹์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ๋„์žˆ๋‹ค. (์–ด๋Žํ„ฐ์— ๋”ฐ๋ผ ๋‹ค๋ฆ„)

app.js ํŒŒ์ผ ๋‚ด ๋ชจ๋“ˆ์ด ์ดˆ๊ธฐํ™”๋  ๋•Œ(var keycloak = new Keycloak({ store: memoryStore });)
ํ•ด๋‹น ์„ค์ •์„ ์‚ฌ์šฉํ•˜์—ฌ Keycloak ์„œ๋ฒ„์™€์˜ ํ†ต์‹ ์„ ์„ค์ •ํ•œ๋‹ค.

# ch7/nodejs/frontend/keycloak.json
{
  "realm": "myservice",
  "auth-server-url": "${env.KC_URL:http://localhost:8080}",
  "resource": "nodejs",
  "credentials" : {
    "secret" : "io9bBSukiwEyUekE62g88aeI4CKYUBTi"
  }
}

 

app.js์—์„œ๋Š” keycloak๋ชจ๋“ˆ์„ ๋ถˆ๋Ÿฌ์˜ค๊ณ  ์ดˆ๊ธฐํ™” ํ•œ๋‹ค. 

keycloak.json ๊ทธ๋ฆฌ๊ณ  app.js ๋‘ ๊ณณ์—์„œ client secret์ด ๋ชจ๋‘ ์‚ฌ์šฉ๋˜์—ˆ๋Š”๋ฐ, ๋ชฉ์ ์ด ๋‹ค๋ฅด๋‹ค.

  • [app.js] Express ์„ธ์…˜์˜ secret:
    • Express์˜ ์„ธ์…˜ ๋ฏธ๋“ค์›จ์–ด๋Š” ์‚ฌ์šฉ์ž ์„ธ์…˜์„ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋œ๋‹ค.
    • app.js์— ์‚ฌ์šฉ๋œ secret์€ ์„ธ์…˜ ID ์ฟ ํ‚ค๋ฅผ ์„œ๋ช…ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋˜์–ด, ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„ ๊ฐ„์˜ ์„ธ์…˜ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณ€์กฐ๋˜์ง€ ์•Š๋„๋ก ๋ณดํ˜ธํ•œ๋‹ค.
  • [keycloak.json] Keycloak์˜ secret:
    • Keycloak์˜ secret์€ ํด๋ผ์ด์–ธํŠธ ์ธ์ฆ์„ ์œ„ํ•ด ์‚ฌ์šฉ๋œ๋‹ค. 
    • Keycloak ์„œ๋ฒ„์™€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐ„์˜ ์•ˆ์ „ํ•œ ํ†ต์‹ ์„ ๋ณด์žฅํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋˜๋ฉฐ, keycloak.json ํŒŒ์ผ์— ์ €์žฅ๋œ๋‹ค.
    • Keycloak์ด ํด๋ผ์ด์–ธํŠธ๋ฅผ ์‹๋ณ„ํ•˜๊ณ  ์ธ์ฆํ•˜๋Š” ๋ฐ ํ•„์š”ํ•˜๋‹ค.

var memoryStore = new session.MemoryStore(); ๋ถ€๋ถ„์€ ์„ธ์…˜๋ฐ์ดํ„ฐ๋ฅผ ๋กœ์ปฌ ๋ฉ”๋ชจ๋ฆฌ์— ์ €์žฅํ•˜๊ฒ ๋‹ค๋Š” ์„ค์ •์ด๋ฉฐ,
๋ฉ”๋ชจ๋ฆฌ ์Šคํ† ์–ด๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ๊ฐœ๋ฐœํ™˜๊ฒฝ์—์„œ๋งŒ ์‚ฌ์šฉ๋œ๋‹ค. (์„œ๋ฒ„๊ฐ€ ์žฌ์‹œ์ž‘๋˜๋ฉด ์„ธ์…˜๋ฐ์ดํ„ฐ ๋ชจ๋‘ ์ง€์›Œ์ง) 
๋”ฐ๋ผ์„œ ๋ณดํ†ต์€ ๋ณ„๋„์˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค(Mongodb, rdb, redis) ๋“ฑ์„ ํ™œ์šฉํ•˜์—ฌ ์„ธ์…˜์„ ๊ด€๋ฆฌํ•œ๋‹ค.

var express = require('express');
var session = require('express-session');
// Keycloak ๋ชจ๋“ˆ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ
// ์ด ๋ชจ๋“ˆ์€ Node.js ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ Keycloak๊ณผ์˜ ํ†ตํ•ฉ์„ ์ง€์›ํ•จ
var Keycloak = require('keycloak-connect');
var cors = require('cors');

var app = express();

app.use(cors());

// ๋ฉ”๋ชจ๋ฆฌ์Šคํ† ์–ด ์„ค์ •
// ์„ธ์…˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๊ธฐ ์œ„ํ•ด ๋ฉ”๋ชจ๋ฆฌ ์Šคํ† ์–ด๋ฅผ ์ƒ์„ฑ
var memoryStore = new session.MemoryStore();

app.use(session({
    secret: 'io9bBSukiwEyUekE62g88aeI4CKYUBTi',
    resave: false,
    saveUninitialized: true,
    store: memoryStore
}));

// Keycloak ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ
// Keycloak ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•˜๊ณ , ์„ธ์…˜ ์Šคํ† ์–ด๋ฅผ ์ „๋‹ฌํ•œ๋‹ค. 
// ์ด ์ธ์Šคํ„ด์Šค๋Š” Keycloak๊ณผ์˜ ์ƒํ˜ธ์ž‘์šฉ์„ ๊ด€๋ฆฌํ•œ๋‹ค.
var keycloak = new Keycloak({ store: memoryStore });

// Keycloak ๋ฏธ๋“ค์›จ์–ด ์‚ฌ์šฉ
// Keycloak ๋ฏธ๋“ค์›จ์–ด๋ฅผ Express ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์ถ”๊ฐ€ํ•œ๋‹ค.
// ์ด ๋ฏธ๋“ค์›จ์–ด๋Š” ์š”์ฒญ์„ ๊ฐ€๋กœ์ฑ„๊ณ , ์ธ์ฆ ๋ฐ ๊ถŒํ•œ ๋ถ€์—ฌ๋ฅผ ์ฒ˜๋ฆฌํ•œ๋‹ค.
app.use(keycloak.middleware());

// Keycloak์œผ๋กœ ๋ณดํ˜ธํ•  ๊ฒฝ๋กœ ์„ค์ •
// keycloak.protect()๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŠน์ • ๊ฒฝ๋กœ๋ฅผ ๋ณดํ˜ธํ•œ๋‹ค. 
// ์ด ๊ฒฝ๋กœ์— ์ ‘๊ทผํ•˜๋ ค๋ฉด ์‚ฌ์šฉ์ž๊ฐ€ ์ธ์ฆ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
app.get('/', keycloak.protect(), function (req, res) {
    res.setHeader('content-type', 'text/plain');
    res.send('Welcome!');
});

app.listen(8000, function () {
    console.log('Started at port 8000');
});

 

 


Nodejs ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํ†ตํ•ฉ (backend)

 

์‹ค์Šต ์ง„ํ–‰

์‹ค์Šต์„ ์ง„ํ–‰ํ•ด๋ณด์ž. ์œ„์—์„œ ์ƒ์„ฑํ•œ client๋ฅผ ์žฌํ™œ์šฉํ•œ๋‹ค.

  • client id: nodejs
  • root URL: http://localhost:8000
  • client authentication: on

์ฝ”๋“œ๋ฅผ ์ค€๋น„ํ•œ๋‹ค.

# ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ๋‹ค์šด๋กœ๋“œ
git clone https://github.com/PacktPublishing/Keycloak---Identity-and-Access-Management-for-Modern-Applications-2nd-Edition.git
cd ch7/nodejs/backend

keycloak ์„ค์ •์— ๋งž๊ฒŒ ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•œ๋‹ค.
์œ„์—์„œ ๋ณต์ œํ•œ client secret์œผ๋กœ ๊ฐ’์„ ๋ณ€๊ฒฝํ•˜๊ณ , 8001ํฌํŠธ์—์„œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์‹œ์ž‘๋  ์ˆ˜ ์žˆ๋„๋ก ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•œ๋‹ค.

// ch7/nodejs/backend/app.js
var express = require('express');
var Keycloak = require('keycloak-connect');

var app = express();

var keycloak = new Keycloak({});

app.use(keycloak.middleware());

app.get('/hello', keycloak.protect(), function (req, res) {
  res.setHeader('content-type', 'text/plain');
  res.send('Access granted to protected resource');
});

app.listen(8001, function () {
  console.log('Started at port 8001');
});
# ch7/nodejs/backend/keycloak.json
{
  "realm": "myservice",
  "bearer-only": true,
  "auth-server-url": "${env.KC_URL:http://localhost:8080}",
  "resource": "nodejs"
}

์ด์ œ ์‹คํ–‰ํ•ด๋ณด์ž.

# ๊ฒฝ๋กœ ์ž˜ ๋“ค์–ด์™€์žˆ๋Š”์ง€ ํ•œ๋ฒˆ ๋” ํ™•์ธ
cd Keycloak---Identity-and-Access-Management-for-Modern-Applications-2nd-Edition/ch7/nodejs/backend
# ์‹œ์ž‘
npm install
npm start

๋กœ์ปฌ ๋ธŒ๋ผ์šฐ์ €์—์„œ http://localhost:8001์— ์ ‘์†ํ•˜๋ฉด ์•„๋ž˜์ฒ˜๋Ÿผ Cannot GET / ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

๊ทธ ์ด์œ ๋Š” AccessToken์ด ์—†๊ธฐ๋•Œ๋ฌธ์ด๋‹ค. 
CLI๋กœ AccessToken์„ ๊ฐ€์ ธ์™€๋ณด์ž.

# curl -X POST http://localhost:8080/realms/myservice/protocol/openid-connect/token \
#	 -u "nodejs:io9bBSukiwEyUekE62g88aeI4CKYUBTi" \
#	 -H "Content-Type: application/x-www-form-urlencoded" \
#	 -d "username=test01" -d "password=1234"  \
#	 -d "grant_type=password" | jq -r '.access_token'

curl -X POST http://localhost:8080/realms/${realm}/protocol/openid-connect/token \
    -u "${client_id}:${client_secret}" \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -d "username=${user_name}" -d "password=${user_password}" \
    -d "grant_type=password" | jq -r '.access_token'

๊ฒฐ๊ณผ๋กœ Access Token์„ ์–ป์—ˆ๋‹ค. (์ฐธ๊ณ : ๋ธŒ๋ผ์šฐ์ €๋ฅผ ํ†ตํ•˜์ง€ ์•Š๊ณ  ์‚ฌ์šฉ์žID/PW๋ฅผ ์ด์šฉํ•˜์—ฌ ์ ‘๊ทผํ† ํฐ์„ ๋ฐ›์œผ๋ ค๋ฉด  Direct access grants์ด ํ—ˆ์šฉ๋˜์–ด์žˆ์–ด์•ผ ํ•˜๋ฉฐ, keycloak์€ ๊ธฐ๋ณธ๊ฐ’์ด ํ—ˆ์šฉ์ด๋‹ค.)

์ด์ œ ์ด Access Token์„ ๊ฐ€์ง€๊ณ  Backend ์„œ๋ฒ„์— ์ ‘๊ทผํ•ด๋ณด์ž.

# ์˜ˆ์‹œ, ๊ฐ ๋ณ€์ˆ˜ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์„ค์ • ํ•„์š”
# export access_token=$(curl -X POST http://localhost:8080/realms/myservice/protocol/openid-connect/token \
# -u "nodejs:io9bBSukiwEyUekE62g88aeI4CKYUBTi" \
# -H "Content-Type: application/x-www-form-urlencoded" \
# -d "username=test01" -d "password=1234"  \
# -d "grant_type=password" | jq -r '.access_token')

export access_token=$(curl -X POST http://localhost:8080/realms/${realm}/protocol/openid-connect/token \
-u "${client_id}:${client_secret}" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=${user_name}" -d "password=${user_password}" \
-d "grant_type=password" | jq -r '.access_token')

echo $access_token

curl -v GET http://localhost:8001/hello \
-H "Authorization: Bearer "$access_token

๊ทธ๋Ÿผ ์•„๋ž˜์™€ ๊ฐ™์€ ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์˜จ๋‹ค.

๋งŒ์•ฝ AccessToken ์—†์ด ์š”์ฒญํ•œ๋‹ค๋ฉด ์‹คํŒจํ•˜๊ฒŒ ๋œ๋‹ค.

 

์„ค๋ช…

์—ฌ๊ธฐ์—์„œ ์‹ค์Šตํ•œ nodejs/backend ๋Š” Keycloak์„ ์‚ฌ์šฉํ•ด์„œ ์‚ฌ์šฉ์ž๋ฅผ ์ธ์ฆํ•˜๋Š”๊ฒƒ์ด ์•„๋‹Œ,
AccessToken์„ ํ™œ์šฉํ•˜์—ฌ ์ธ์ฆ๋œ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฆฌ์†Œ์Šค ์„œ๋ฒ„๋กœ์„œ์˜ ์—ญํ• ์„ ํ•œ๋‹ค.

// ch7/nodejs/backend/app.js
var express = require('express');
var Keycloak = require('keycloak-connect');

var app = express();

var keycloak = new Keycloak({});

// keycloak ๋ฏธ๋“ค์›จ์–ด ์‚ฌ์šฉ
app.use(keycloak.middleware());

// ๋ณดํ˜ธ๋œ ๋ฆฌ์†Œ์Šค์— ์š”์ฒญ์ด์˜ค๋ฉด Keycloak ์„œ๋ฒ„์— ํ•ด๋‹น ํ† ํฐ์˜ ์œ ํšจ์„ฑ์„ ๊ฒ€์ฆ
app.get('/hello', keycloak.protect(), function (req, res) {
  res.setHeader('content-type', 'text/plain');
  res.send('Access granted to protected resource');
});

app.listen(8001, function () {
  console.log('Started at port 8001');
});

keycloak.protect()๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŠน์ • ๊ฒฝ๋กœ์— ๋Œ€ํ•œ ์ ‘๊ทผ์„ ๋ณดํ˜ธํ•˜๋ฉฐ, ์ž‘๋™๋ฐฉ์‹์€ ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

  1. AccessToken ์ˆ˜์‹ : ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์š”์ฒญ์„ ๋ณด๋‚ผ ๋•Œ, Authorization ํ—ค๋”์— AccessToken์„ ํฌํ•จํ•œ๋‹ค.
  2. ํ† ํฐ ๊ฒ€์ฆ: keycloak.protect() ๋ฏธ๋“ค์›จ์–ด๊ฐ€ ์š”์ฒญ์„ ๊ฐ€๋กœ์ฑ„๊ณ , Keycloak ์„œ๋ฒ„์— ํ•ด๋‹น ํ† ํฐ์˜ ์œ ํšจ์„ฑ์„ ๊ฒ€์ฆํ•œ๋‹ค.
  3. ์ธ์ฆ ์ฒ˜๋ฆฌ: ํ† ํฐ์ด ์œ ํšจํ•˜๋ฉด ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๊ณ , ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ์ ‘๊ทผ์ด ๊ฑฐ๋ถ€๋œ๋‹ค.
728x90