์ฐธ๊ณ ์๋ฃ
* ์ฑ : https://www.yes24.com/Product/Goods/122459785
* ํ ์คํธ ์์ค์ฝ๋: https://github.com/PacktPublishing/Keycloak---Identity-and-Access-Management-for-Modern-Applications-2nd-Edition
์ค์ต ์ค๋น) Keycloak & ํ ์คํธ ์ ํ๋ฆฌ์ผ์ด์ ์ธํ
Keycloak ์ค๋น
Keycloak์ ๋ก์ปฌ์์ ๋จผ์ ์คํํ๋ค.
docker run -p 8080:8080 \
-e KEYCLOAK_ADMIN=admin \
-e KEYCLOAK_ADMIN_PASSWORD=admin \
quay.io/keycloak/keycloak \
start-dev
ํ ์คํธ ์ดํ๋ฆฌ์ผ์ด์ ์ค๋น
์ดํ ํ ์คํธ ์ฝ๋ ์ดํ๋ฆฌ์ผ์ด์ ์ ์คํํ๋ค.
# Backend ์ดํ๋ฆฌ์ผ์ด์
์คํ
git clone https://github.com/PacktPublishing/Keycloak---Identity-and-Access-Management-for-Modern-Applications-2nd-Edition.git
cd Keycloak---Identity-and-Access-Management-for-Modern-Applications-2nd-Edition/ch5/backend
npm install
npm start
# Frontend ์ดํ๋ฆฌ์ผ์ด์
์คํ
cd Keycloak---Identity-and-Access-Management-for-Modern-Applications-2nd-Edition/ch5/frontend
npm install
npm start
# ํ
์คํธ ์ดํ๋ฆฌ์ผ์ด์
์ ์
http://localhost:8000
Keycloak Client ์ค๋น
- client id: oauth-playground
- access type: public
- vaild redirect URLs: http://localhost:8000/
- web origin: http://localhost:8000
์ด๋ Keyclock client์์ access type์ ์ง์ ๋ณ๊ฒฝํ ์ ์๋ ๋ถ๋ถ์ ์ฐพ์ ์ ์์ํ
๋ฐ,
์๋ ์บก์ณ์ฒ๋ผ Client authentication๊ฐ OFF ๋์ด์์ผ๋ฉด ๋๋ค. ๊ธฐ๋ณธ๊ฐ์ด OFF์ด๋ฏ๋ก ์ค์ ์ ๋ฐ๊ฟํ์ ์๋ค.
Keycloak Users ์ค๋น
Clients ๋ฟ๋ง ์๋๋ผ ์ ์ํ ์ ์๋ User๋ฅผ ์์ฑํด๋๋ค.
ํ
์คํธ ์ ์ ๋ ์์ ID๋ก ์์ฑํ ํ (์ฌ๊ธฐ์์๋ user01๋ก ์์ฑํ๋ค), Credentialsํญ์์ ๋น๋ฐ๋ฒํธ๋ฅผ ์ค์ ํ๋ค.
์ดํ Realm roles์์ myrole์ ์์ฑํ๋ค.
myrole ์ด๋ผ๋ ์ด๋ฆ์ผ๋ก role์ ์์ฑํ๋ค๋ฉด, ์์์ ์์ฑํ user01์ ๋ค์ ์ ์ํ์ฌ Role Mapping์์ myrole์ ์ถ๊ฐํ๋ค.
์ด ์์
์ ํ๋ ์ด์ ๋ ch5/backend/app.js ์ฝ๋์์ keycloak.protect('realm:myrole')๋ก ์๋ํฌ์ธํธ๋ฅผ ๋ณดํธํ๊ณ ์๊ธฐ ๋๋ฌธ์ด๋ค.
์ฌ์ฉ์๊ฐ myrole์ ๋งคํ๋์ด์์ง ์๋ค๋ฉด Invoke service ๋จ๊ณ์์ ํ
์คํธ์ ์คํจํ๋ค.
์ค์ตํ๊ธฐ์ ์) Access Token์ ์ ๊ทผ๊ถํ ์ ํ์ ๋ํด ์ดํดํ๊ธฐ
AccessToken์ ๋ฆฌ์์ค์๋ฒ(์ค์ต์์๋ Backend)์ ์ ๊ทผํ๋๋ฐ ์ฌ์ฉํ๋ Token์ด๋ค.
์ด ํ ํฐ์ผ๋ก ๋ฆฌ์์ค์ ์ ๊ทผํ ์ ์๊ธฐ ๋๋ฌธ์ ์ ๊ทผ๊ถํ์ ์ ์ดํ๋๊ฒ์ด ๋งค์ฐ ์ค์ํ๋ค.
์ ํ๋ฆฌ์ผ์ด์ ์ ๋ณด์์ ๊ฐํํ๊ณ , ์ฌ์ฉ์๊ฐ ์ ๊ทผํ ์ ์๋ ๋ฆฌ์์ค๋ฅผ ์ธ๋ฐํ๊ฒ ์ ์ดํ๊ธฐ ์ํด AccessToken์ ์ ๊ทผ ๊ถํ ์ ์ด ๋ฉ์ปค๋์ฆ์ ๋ํด ํ์ธํ๊ณ ๋ค์์ผ๋ก ๋์ด๊ฐ์!
Keycloak Authorization Service Guide
์ ๊ทผ์ ํ ๋ฉ์ปค๋์ฆ | ์ค๋ช |
Attribute-based access control (ABAC) |
์ฌ์ฉ์, ๋ฆฌ์์ค, ํ๊ฒฝ์ ์์ฑ์ ๊ธฐ๋ฐ์ผ๋ก ์ ๊ทผ์ ์ ์ด ์: ์ฌ์ฉ์์ ๋ถ์, ์ง๊ธ, ์์น ๋ฑ์ ์์ฑ์ ๊ธฐ๋ฐ์ผ๋ก ์ ๊ทผ ๊ถํ ํ์ฉ |
Role-based access control (RBAC) |
์ฌ์ฉ์์๊ฒ ํ ๋น๋ ์ญํ ์ ๊ธฐ๋ฐ์ผ๋ก ์ ๊ทผ์ ์ ์ด ์: Realm Role์ ๋งคํ๋ ์ฌ์ฉ์๋ง ์ ๊ทผ ๊ถํ ํ์ฉ |
User-based access control (UBAC) |
ํน์ ์ฌ์ฉ์๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ ๊ทผ์ ์ ์ด (๊ฐ๋ณ ์ฌ์ฉ์ ๋จ์์ ์ธ๋ฐํ ๊ถํ ์ ์ด ๊ฐ๋ฅ) ์, user01์๊ฒ๋ง ์ ๊ทผ ๊ถํ ํ์ฉ |
Context-based access control (CBAC) |
์์ฒญ์ ์ปจํ
์คํธ(์๊ฐ, ์์น, ๋๋ฐ์ด์ค ๋ฑ)๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ ๊ทผ์ ์ ์ด ์: ํน์ IP ๋ฒ์์์๋ง ์ ๊ทผ ํ์ฉ |
Rule-based access control: | JavaScript๋ฅผ ์ฌ์ฉํ ์ปค์คํ ๊ท์น ์ ์ (๋ณต์กํ ๋น์ฆ๋์ค ๋ก์ง์ ๊ตฌํํ ์ ์์) |
Time-based access control: | ์๊ฐ ๊ธฐ๋ฐ์ ์ ๊ทผ ์ ์ด (ํน์ ์๊ฐ๋๋ ๊ธฐ๊ฐ ๋์๋ง ์ ๊ทผ ํ์ฉ) |
Custom ACMs through SPI: | Service Provider Interface๋ฅผ ํตํ ์ปค์คํ
์ ๊ทผ ์ ์ด ๋ฉ์ปค๋์ฆ ๊ตฌํ ๊ฐ๋ฅ (ํ์ํ ๊ฒฝ์ฐ ์์ฒด ์ ์ฑ ํ์ ์ ๊ฐ๋ฐํ์ฌ ํ์ฅ ๊ฐ๋ฅ) |
์ด๋ฐ ์ ๊ทผ ๊ถํ ๋ฉ์ปค๋์ฆ์ ๊ตฌํํ๊ธฐ ์ํด ๋ฐ๋์ ์ดํดํ๊ณ ์์ด์ผํ Keycloak ๊ฐ๋
์ด ์๋ค.
์๋ ์ค์ต์์๋ ๊ณ์ํด์ ๋ณผ ์์ ์ด๋ค.
- Role:
- Role์ ์์คํ ๋ด์์ ์ํํ ์ ์๋ ์์ ์ ์ ์ํ๋ค
- RBAC ๊ตฌํ์ ์ฌ์ฉ๋๋ค.
- Scpoe:
- ๋ฆฌ์์ค์ ๋ํด ์ํํ ์ ์๋ ์์ ์ด๋ ๊ถํ์ ๋ฒ์๋ฅผ ์ ์ํ๋ค.
- ์๋ฅผ ๋ค์ด, read, write, delete ๋ฑ์ ์์ ์ Scope๋ก ์ ์ํ ์ ์๋ค.
- Audience (aud):
- JWT ํ ํฐ์ aud ํด๋ ์์ ๋งํ๋ค.
- ํ ํฐ์ด ์ฌ์ฉ๋ . ์์๋ ๋์ ๋ฆฌ์์ค ์๋ฒ ํน์ ํด๋ผ์ด์ธํธ๋ฅผ ์ง์ ํ๋ค.
- ํน์ ๋ฆฌ์์ค ์๋ฒ๋ง ํด๋น ํ ํฐ์ ์ฌ์ฉํ ์ ์๋๋ก ์ ์ํ ์ ์๋ค
์ค์ต #1) ๋ฐฑ์๋ ๋ฆฌ์์ค์ ์ ๊ทผํ๊ธฐ (Role ํ์ฉ ํ์ธํ๊ธฐ)
์ค์ตํ๊ธฐ
http://localhost:8000 ์ ์ ์ํ๊ณ , 2 - Authorization๋ฅผ ํด๋ฆญํ๋ค.
Send Authorization Request๋ฅผ ํด๋ฆญํ๋ฉด Access Token ๋ฐ๊ฒ ๋๋ค. ํ์ด์ง์์ ์ธ๋ถ ๋ด์ฉ์ ํ์ธํ ์ ์๋ค.
3 - Invoke Service ๋ก ๋ค์ด๊ฐ์ Invvoke๋ฅผ ํด๋ฆญํ๋ฉด Response๊ฐ์ผ๋ก Secret message!๋ฅผ ๋ณผ ์ ์๋ค.
์ดํดํ๊ธฐ
์ด๋ฒ ์ค์ต์์๋ Frontend์์ Backend์๋ฒ์ AccessToken์ ํตํด ์ ๊ทผํ๋ ๋ถ๋ถ์ ์ค์ตํ๊ฒ์ด๋ค.
๋ค์ด์ด๊ทธ๋จ์ผ๋ก ํ๊ธฐํ์๋ฉด ์๋์ ๊ฐ๋ค.
- Access Token ๊ฒ์ฆ (6-7๋จ๊ณ)
- Role ํ์ธ (8๋จ๊ณ)
- ๊ถํ์ด ํ์ธ๋ ๊ฒฝ์ฐ์๋ง Secret Message! ๋ฐํ (9๋จ๊ณ)
[ํด๋ผ์ด์ธํธ(frontend)] [Keycloak] [๋ฆฌ์์ค ์๋ฒ(backend)]
โ โ โ
โ 1. ์ธ์ฆ ์์ฒญ โ โ
โโโโโโโโโโโโโโโโโโโโโโโโ>โ โ
โ โ โ
โ 2. Authorization Codeโ โ
โ<โโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ 3. Token ๊ตํ โ โ
โโโโโโโโโโโโโโโโโโโโโโโโ>โ โ
โ โ โ
โ 4. Access Token โ โ
โ<โโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ 5. Access Token์ผ๋ก ๋ฆฌ์์ค ์์ฒญ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ>โ
โ โ โ
โ โ 6. Token ๊ฒ์ฆ ์์ฒญ โ
โ โ<โโโโโโโโโโโโโโโโโโโโโโโโ
โ โ โ
โ โ 7. Token ์ ํจ์ฑ ์๋ต โ
โ โโโโโโโโโโโโโโโโโโโโโโโโ>โ
โ โ โ
โ โ 8. realm:myrole โ
โ โ ๊ถํ ํ์ธ โ
โ โ <โโโโโโโโ โ
โ โ โ โ
โ 9. Secret Message! ์๋ต โ
โ<โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
์ค์ต #2) ์ ๊ทผ๊ถํ ๋ถ์ฌ ๋์(Concent) ์์ฒญํ๊ธฐ (Scope ํ์ฉ ํ์ธํ๊ธฐ)
์ค์ตํ๊ธฐ
http://localhost:8080 Keycloak ์ ์ ์ํ๊ณ , Clients -> oauth-playground ์์ ์๋ ์ค์ ์ ๋ณ๊ฒฝํ๋ค.
- Consent required: On
- Display client on screen: On
- Consent screen text: ๊ถํ(Concent) ๋ถ์ฌ ๋์ ์์ฒญ
์ด์ ๋ค์ http://localhost:8000/๋ก ์ ๊ทผํ๊ณ 2-Authorization์์ ๋ค์ํ๋ฒ Send Authorization Request๋ฅผ ํด๋ฆญํ๋ค.
์ด๋ฏธ ๋ก๊ทธ์ธํ ์ธ์
์ด ์๋ ๊ฒฝ์ฐ Grant Access ํ์
์ด ๋์ค๊ณ , ๋ก๊ทธ์ธ์ธ์
์ด ์๋๊ฒฝ์ฐ์๋ ๋ค์ํ๋ฒ ๋ก๊ทธ์ธ ํ๋๋ก ํ๋ค.
์ด์ ์ด ์ฌ์ฉ์์ Consents๋ฅผ ํ์ธํด๋ณด์.
http://localhost:8080 Keycloak ์ ์ ์ํ๊ณ , Users -> user01 -> Consentsํญ์ ํ์ธํด๋ณด์.
์ด ์ฌ์ฉ์์๊ฒ ๋ถ์ฌ๋ ๊ถํ์ ํ์ธํ ์ ์๋ค.
์ด๋ฒ์๋ ์๋ก์ด Client Scope๋ฅผ ์์ฑํ๊ณ ์ฌ์ฉ์์๊ฒ ์ถ๊ฐํด๋ณด์.
Client Scopes์์ ์๋ก์ด Client Scope๋ฅผ ์์ฑํ๋ค.
- Name: (์๋ฌด๊ฑฐ๋) my-custom-scope
- Display on consent screen: On
์ด์ Clients -> oauth-playground -> Client Scopeํญ์ผ๋ก ์ด๋ํ๊ณ , ์์์ ์์ฑํ my-custom-scope๋ฅผ ์ถ๊ฐํ๋ค.
์ด์ ๋ค์ http://localhost:8000/๋ก ์ ๊ทผํ๊ณ ๋ค์ํ๋ฒ Send Authorization Request๋ฅผ ํด๋ฆญํ๋ค.
์ด๋ ์์์ ๋์์์ฒญ ํ์ด์ง์์ ์ถ๊ฐํ ์๋ก์ด Scope๋ฅผ ํ์ธํ ์ ์๋ค.
์ฌ์ฉ์์๊ฒ ์ด๋ค Scope๊ฐ ํ์ฉ๋์๋์ง๋ Users -> ์ฌ์ฉ์ -> Consents ํญ์์ ํ์ธํ ์ ์๋ค.
Scope๋ Keycloak User์ Consents ํญ์์ ์ ๊ฑฐํ ์๋ ์๋ค.
์ ๊ฑฐํ ๊ฒฝ์ฐ ์ฌ์ฉ์ ๋์๋ฅผ ๋ค์ ๋ฐ์์ผ ํ๋ค.
์ดํดํ๊ธฐ
Scope (๊ถํ ๋ฒ์)๋ ํด๋ผ์ด์ธํธ๊ฐ ์์ฒญํ ์ ์๋ ๊ถํ์ ๋ฒ์๋ฅผ ์ ์ํ๋ค.
- openid: OIDC ์ธ์ฆ ์์ฒญ
- profile: ์ฌ์ฉ์ ํ๋กํ ์ ๋ณด
- email: ์ด๋ฉ์ผ ์ ๋ณด
- address: ์ฃผ์ ์ ๋ณด
- phone: ์ ํ๋ฒํธ ์ ๋ณด
Consent (์ฌ์ฉ์ ๋์)๋ ์ฌ์ฉ์์๊ฒ ํด๋ผ์ด์ธํธ์ ์ค์ฝํ ์์ฒญ์ ๋ํ ๋์๋ฅผ ๋ฐ๋ ๊ณผ์ ์ ๋งํ๋ฉฐ,
์ฌ์ฉ์๊ฐ ๋ช
์์ ์ผ๋ก ๊ถํ ๋ถ์ฌ๋ฅผ ์น์ธํ๋๊ฒ์ด๋ค.
[Client(frontend)] [Auth Server(Keycloak)] [Resource Server(backend)]
โ โ โ
โ โ โ
โโโโโโ 1. Scope ์์ฒญ โโโโโ>โ โ
โ โ โ
โ<โโโ 2. Consent Screen โโโ โ
โ โ โ
โโโโโโ 3. ์ฌ์ฉ์ ๋์ โโโโโโ>โ โ
โ โ โ
โ<โโโ 4. Auth Code โโโโโโโโ โ
โ โ โ
โโโโโโ 5. Code ๊ตํ โโโโโโ>โ โ
โ โ โ
โ<โโโ 6. Access Token โโโโโ โ
โ โ โ
โโโโโโโโโโโโโโโ 7. Access Token์ผ๋ก ์์ฒญ โโโโโโโโโโโโโโ>โ
โ โ โ
โ<โโโโโโโโโโโโโโโโ 8. Protected Resource โโโโโโโโโโโโโโ
Scope๋ฅผ Sample์ดํ๋ฆฌ์ผ์ด์ ์์๋ ์๋์ ๊ฐ์ด ์์ ํ์ฌ ํ์ฉํ ์ ์๋ค. (์ํ์ฝ๋์์๋ ๊ตฌํ๋์ด์์ง ์๋ค)
# AS-IS
app.get('/secured', keycloak.protect('realm:myrole'), function (req, res) {
res.setHeader('content-type', 'text/plain');
res.send('Secret message!');
});
# TO-BE
# ๋จ์ผ ์ค์ฝํ ํ์ธ
app.get('/secured', keycloak.protect('scope:my-custom-scope'), function (req, res) {
res.send('Secret message!');
});
# ์ฌ๋ฌ ์ค์ฝํ ํ์ธ
app.get('/secured', keycloak.protect(['scope:my-custom-scope', 'scope:another-scope']), function (req, res) {
res.send('Secret message!');
});
์ค์ต #3) Audience๋ก ์ ๊ทผ๊ถํ ์ ํ ํ๊ธฐ (Audience ํ์ฉ ํ์ธํ๊ธฐ)
์ค์ตํ๊ธฐ
ch5/backend/keycloak.json ํ์ผ์ ์์ ํ๋ค.
{
"realm": "myrealm",
"bearer-only": true,
"auth-server-url": "${env.KC_URL:http://localhost:8080}",
"resource": "oauth-playground", // ์์์ ์์ฑํ Client์ด๋ฆ์ผ๋ก ์์
"verify-token-audience": true // true๋ก ์์
}
- realm: Keycloak์์ ์ธ์ฆ์ ๊ด๋ฆฌํ๋ ๋ ผ๋ฆฌ์ ๋จ์
- bearer-only: ํด๋ผ์ด์ธํธ๊ฐ ์ฌ์ฉ์ ์ธํฐํ์ด์ค ์์ด ๋ฐฑ์๋ ์๋น์ค๋ก๋ง ์ฌ์ฉ๋ ๋ ์ค์
true๋ก ์ค์ ํ๋ฉด Keycloak์ด ์ด ํด๋ผ์ด์ธํธ์ ๋ํด ๋ก๊ทธ์ธ ํ์ด์ง๋ฅผ ์ ๊ณตํ์ง ์์ - auth-server-url: Keycloak ์ธ์ฆ ์๋ฒ์ URL
- resource: ํด๋ผ์ด์ธํธ ID, Keycloak์์ ํด๋ผ์ด์ธํธ๋ฅผ ์๋ณํ๋ ๋ฐ ์ฌ์ฉํจ
- verify-token-audience: ํ ํฐ์ aud(Audience) ํด๋ ์์ ๊ฒ์ฆํ ์ง ์ฌ๋ถ ์ค์
true๋ก ์ค์ ํ๋ฉด ํ ํฐ์ด ์ฌ๋ฐ๋ฅธ ๋์ ํด๋ผ์ด์ธํธ๋ฅผ ์ํด ๋ฐ๊ธ๋์๋์ง ํ์ธํจ
์ด์ http://localhost:8000/ ์ ์ ๊ทผํ์ฌ Send Authorization Request๋ฅผ ํด๋ฆญํ์ฌ Access Token์ ๋ฐ๊ธ๋ฐ๊ณ
3-Invoke Service์์ Invoke๋ฅผ ํด๋ฆญํด๋ณด์.
์ด๋ Access Deny๊ฐ ๋ณด์ธ๋ค.
"resource": "oauth-playground"๋ ํด๋ผ์ด์ธํธ ID๋ฅผ ์๋ฏธํ๊ณ , "verify-token-audience": true๋ก ์ค์ ๋์ด ์์ผ๋ฏ๋ก
"aud" ๊ฐ์ ClientID๊ฐ ํฌํจ๋์ด์ผ ํ๋๋ฐ account๋ง ์์ด์ Access Deny๊ฐ ๋์๋ค.
์ด์ aud๊ฐ์ ClientID๊ฐ ์ถ๋ ฅ๋๋๋ก ์์ ํด๋ณด์
๋จผ์ , Client(http://localhost:8080/admin/master/console/#/myrealm/clients/) ์์ oauth-playground์ ํด๋ฆญํ๋ค.
Clients scope ํญ์์ oauth-playground-dedicated๋ฅผ ํด๋ฆญํ๋ค. (ํด๋ผ์ด์ธํธ๋ณ๋ก ๊ณ ์ ํ ๊ถํ์ด๋ ์์ฑ์ ์ ์ํ ๋ ์ฌ์ฉ)
Mapper ํญ์์ Configure a new mapper๋ฅผ ํด๋ฆญํ๊ณ , Audience๋ฅผ ์ ํํ๋ค.
client id์ ๊ฐ์ ์ด๋ฆ(oauth-playground)์ Audiance type์ ๋งคํผ๋ฅผ ์์ฑํ๋ค.
์ด์ ๋ค์ํ๋ฒ http://localhost:8000/ ์ ์ ๊ทผํ์ฌ Send Authorization Request๋ฅผ ํด๋ฆญํ์ฌ Access Token์ ๋ฐ๊ธ๋ฐ๊ณ
3-Invoke Service์์ Invoke๋ฅผ ํด๋ฆญํด๋ณด์.
์ด์ aud ํด๋ ์์ ClientID์ ๊ฐ์ ๊ฐ์ด ๋ค์ด์์ด Access ๊ฐ ํ์ฉ ๋์๋ค.
Audience ์ดํดํ๊ธฐ
verify-token-audience ๊ฐ์ False ์์ True๋ก ์์ ํ๋ฉด, Audience ๊ฒ์ฆ ํ์ฑํ๋๋ค.
Audience ๊ฒ์ฆ์ ์๋ชป๋ ๋์ ํด๋ผ์ด์ธํธ์์ ํ ํฐ์ ์ฌ์ฉํ๋ ๊ฒ์ ๋ฐฉ์งํ๋ค. ํ ํฐ์ด ์๋๋ ๋ฆฌ์์ค ์๋ฒ์์๋ง ์ฌ์ฉ๋๋๋ก ๋ณด์ฅํ๋ค.
- ๋ณ๊ฒฝ ์ (false): Audience ๊ฒ์ฆ ๋นํ์ฑํ
- ํ ํฐ์ aud(Audience) ํด๋ ์์ ๊ฒ์ฆํ์ง ์๋๋ค.
- ํ ํฐ์ด ํน์ ํด๋ผ์ด์ธํธ๋ฅผ ๋์์ผ๋ก ๋ฐ๊ธ๋์๋์ง ํ์ธํ์ง ์๊ธฐ ๋๋ฌธ์ ๋ณด์์ ์ทจ์ฝํ๋ค.
- ๋ณ๊ฒฝ ํ (true): Audience ๊ฒ์ฆ ํ์ฑํ
- ํ ํฐ์ aud ํด๋ ์์ ๊ฒ์ฆํ๋ค.
- ํ ํฐ์ด ์ฌ๋ฐ๋ฅธ ๋์ ํด๋ผ์ด์ธํธ๋ฅผ ์ํด ๋ฐ๊ธ๋์๋์ง ํ์ธํ๋ค.
- ํด๋ผ์ด์ธํธ๊ฐ ์์ ์๊ฒ ๋ฐ๊ธ๋ ํ ํฐ๋ง ์๋ฝํ๊ฒ ๋์ด ๋ณด์์ด ๊ฐํ๋๋ค.
์ค์ต #4) Role๋ก ์ ๊ทผ๊ถํ ์ ํ ํ๊ธฐ (Role ํ์ฉ ํ์ธํ๊ธฐ)
Client์ Full scope allowed ์ดํดํ๊ธฐ
Full scope allowed ์ค์ ์ ํด๋ผ์ด์ธํธ ์ค์ฝํ์ ๊ถํ ๋ฒ์๋ฅผ ์ ์ดํ๋ ์ค์ํ ์ต์
์ด๋ค.
Full scope allowed๊ฐ ๊ธฐ๋ณธ์ ์ผ๋ก ON์ผ๋ก ๋์ด์๋๋ฐ, ์ด์ ๋ ์ด๊ธฐ ์ค์ ์ ํด๋ผ์ด์ธํธ๊ฐ ๋ชจ๋ ์ญํ ์ ์ ๊ทผํ ์ ์๋๋ก ํ์ฌ ์ค์ ์ ํธ์์ฑ์ ์ ๊ณตํ๊ธฐ ์ํจ์ด๋ค.
ํ๋ก๋์
ํ๊ฒฝ์์๋ ๋ณด์์ ์ํด ํ์ํ ์ญํ ๋ง ๋ช
์์ ์ผ๋ก ๋งคํํ์ฌ ์ฌ์ฉํ๋๋ก ํ๋ค.
- Full scope allowed๊ฐ ON์ธ ๊ฒฝ์ฐ:
- ํด๋ผ์ด์ธํธ๋ ๋ชจ๋ ๊ฐ๋ฅํ ์ญํ (Realm ์ญํ ๊ณผ ๋ค๋ฅธ ํด๋ผ์ด์ธํธ์ ์ญํ ํฌํจ)์ ์ ๊ทผํ ์ ์๋ค.
- ๋ณ๋์ ์ญํ ์ค์ฝํ ๋งคํ ์์ด๋ ๋ชจ๋ ์ญํ ์ ๋ํ ์ ๊ทผ์ด ๊ฐ๋ฅํ๋ค.
- ๋ณด์์ ์ฃผ์๊ฐ ํ์ํ ์ค์ ์ด๋ฉฐ, ํ๋ก๋์ ํ๊ฒฝ์์๋ ์ฌ์ฉํ์ง ์๋๋ก ์ฃผ์ํด์ผํ๋ค.
- Full scope allowed๊ฐ OFF์ธ ๊ฒฝ์ฐ:
- ํด๋ผ์ด์ธํธ๋ ๋ช ์์ ์ผ๋ก ๋งคํ๋ ์ญํ ์๋ง ์ ๊ทผํ ์ ์๋ค.
- ์ค์ฝํ ๋งคํ์ ํตํด ํน์ ์ญํ ์ ๋ํ ์ ๊ทผ์ ๊ฐ๋ณ์ ์ผ๋ก ์ค์ ํด์ผ ํ๋ค.
# Full scope allowed: ON
{
"realm_access": {
"roles": ["role1", "role2", "role3"] #realm๋ด์ ๋ชจ๋ ์ญํ ์ ๊ทผ ๊ฐ๋ฅ
}
}
# Full scope allowed: OFF
{
"realm_access": {
"roles": ["role1"] # client์ ๋ช
์์ ์ผ๋ก ๋งคํ๋ ์ญํ ๋ง ์ ๊ทผ ๊ฐ๋ฅ
}
}
์ค์ตํ๊ธฐ
๋จผ์ , ์์์ ์ค๋ช
ํ๋ฐ๋ก Full scope allowed ๋ฅผ OFF๋ก ๋ณ๊ฒฝํ๋ค. (์ ์คํฌ๋ฆฐ์ท ์ฐธ๊ณ )
ON์์ OFF๋ก ๋ณ๊ฒฝํ๋ฉด ์๋์ฒ๋ผ AccessToken์ด ๋ณ๊ฒฝ๋๋ค.
์ค์ต์ค๋น ๋จ๊ณ์์ ์ธ๊ธํ๋ฐ๋ก,
ch5/backend/app.js ์ฝ๋์์๋ keycloak.protect('realm:myrole')๋ก ์๋ํฌ์ธํธ๋ฅผ ๋ณดํธํ๊ณ ์๋ค.
app.get('/secured', keycloak.protect('realm:myrole'), function (req, res) {
res.setHeader('content-type', 'text/plain');
res.send('Secret message!');
});
myrole์ ๋งคํ๋์ด์์ง ์๋ค๋ฉด Invoke service ๋จ๊ณ์์ ํ ์คํธ์ ์คํจํ๋ค.
์ด์ myrole์ ๋ช ์์ ์ผ๋ก Client์ ์ฐ๊ฒฐํด๋ณด์.
Client์ realm_access Role ๋งคํ๋ฐฉ๋ฒ 1) {client id}-dedicated(์ ์ฉ์ค์ฝํ)๋ฅผ ์ฌ์ฉํ์ฌ Role ๋งคํ
oauth-playground์์ Client scope -> {client id}-dedicated -> Scope -> Assign role -> myrole -> Assign์ผ๋ก myrole์ ๋งคํํ๋ค.
์ด์ http://localhost:8000/์์ Access Token์ ํ์ธํด๋ณด์. myrole์ด ํฌํจ๋์ด์๊ณ Invoke๋ ์ฑ๊ณตํ๋ค.
์ค๋ช | ํด๋ผ์ด์ธํธ์ ์๋์ผ๋ก ์์ฑ๋ ์ ์ฉ ์ค์ฝํ({client id}-dedicated)๋ฅผ ์ฌ์ฉํ์ฌ ์ญํ ์ ๋งคํํ๋ค. ์ด ๋ฐฉ๋ฒ์ ํน์ ํด๋ผ์ด์ธํธ์๋ง ์ ์ฉ๋๋ ์ ์ฉ ์ค์ ์ ์ฌ์ฉํ๋ค. |
์ฅ์ | ํด๋ผ์ด์ธํธ๋ณ๋ก ๊ณ ์ ํ ์ค์ ์ ์ ์งํ ์ ์๋ค. |
๋จ์ | ์ ์ฉ ์ค์ฝํ์ ์์กดํ๋ฏ๋ก, ๋ค๋ฅธ ํด๋ผ์ด์ธํธ์ ๋์ผํ ์ค์ ์ ์ ์ฉํ๊ธฐ ์ด๋ ต๋ค. |
Client์ realm_access Role ๋งคํ๋ฐฉ๋ฒ 2) Client Scope๋ฅผ ์ฌ์ฉํ์ฌ Client์ Role ๋งคํ
Client Scope์์ ์๋ก์ด ClientScope๋ฅผ ์์ฑํ๊ณ , Scopeํญ์์ myrole์ ์ฐ๊ฒฐํ๋ค.
์ด์ Client (oauth-playground)์ ์์์ ์์ฑํ Client Scope๋ฅผ ์ถ๊ฐํ๋ค.
๊ทธ๋ผ ์ด์ ๋ค์ํ๋ฒ ์คํํด๋ณด์.
๋ง์ฐฌ๊ฐ์ง๋ก realm_access.role์ myrole์ด ์ถ๊ฐ๋์ด์๊ณ Invoke๊ฐ ์ฑ๊ณตํ๋ค.
์ค๋ช | ์๋ก์ด Client Scope๋ฅผ ์์ฑํ๊ณ , ํด๋น ์ค์ฝํ์ ์ญํ ์ ๋งคํํ ํ, ํด๋ผ์ด์ธํธ์ ์ด ์ค์ฝํ๋ฅผ ์ถ๊ฐํ๋ค. |
์ฅ์ | ์ฌ๋ฌ ํด๋ผ์ด์ธํธ์์ ๋์ผํ ์ค์ ์ ์ฝ๊ฒ ์ฌ์ฌ์ฉํ ์ ์๋ค. ์ค์ฝํ๋ฅผ ํตํด ์ญํ ๊ณผ ๊ถํ์ ์ค์์์ ๊ด๋ฆฌํ ์ ์๋ค. |
๋จ์ | ์ ์ฉํ ํด๋ผ์ด์ธํธ์ ๋์ผํ ์ค์ฝํ๊ฐ ์ ์ฉ๋๋ฏ๋ก, ๊ฐ๋ณ ํด๋ผ์ด์ธํธ์ ๋ํ ์ธ๋ฐํ ์ ์ด๊ฐ ์ด๋ ค์ธ ์ ์๋ค. |
์ค์ต #5) Scope ํ์ฉํ๊ธฐ (Scope ๋ช ๋ช ๊ท์น/ Default&Optional)
Scope ๋ช ๋ช ๊ท์น
Scope์ ๋ช
๋ช
๊ท์น์ ์ ํด์ง ํ์ค์ด ์๋ค. ๋ฆฌ์์ค์ ์์
์ ๋ช
ํํ ๊ตฌ๋ถํ ์ ์๊ฒ ๊ท์น์ ์ ํ๋๊ฒ์ด ๊ถํ๊ด๋ฆฌ์ ์ฉ์ด๋ค.
์๋ฅผ๋ค์ด resource:action ํ์์ผ๋ก ์ง์ ํ ์ ์๋ค. (AWS IAM Policy๋ฅผ ์ฐธ๊ณ ํ ์ ์์)
- ๊ธฐ๋ณธ์ ์ธ CRUD ์์
- users:read
- users:write
- users:delete
- users:update
- ์ธ๋ถ์ ์ธ ๊ถํ๊ตฌ๋ถ
- users:profile:read
- users:settings:write
- ํน์ ๋ฆฌ์์ค์ ๋ชจ๋ ๊ถํ: resource:*
- ๋ชจ๋ ๋ฆฌ์์ค์ ์ฝ๊ธฐ ๊ถํ: *:read
- ๋ชจ๋ ๋ฆฌ์์ค์ ๋ชจ๋ ๊ถํ: *:*
Scope ์ ํ (Default/ Optional)
Keycloak์ Client Scope์๋ Default์ Optional ๋ ๊ฐ์ง ์ ํ์ด ์๋ค.
- Default
- ํด๋ผ์ด์ธํธ๊ฐ ๋ช ์์ ์ผ๋ก ์์ฒญํ์ง ์์๋ ์๋์ผ๋ก ํฌํจ๋๋ ์ค์ฝํ
- ๋ชจ๋ ํ ํฐ ์์ฒญ์ ๊ธฐ๋ณธ์ ์ผ๋ก ํฌํจ๋จ
- ํด๋ผ์ด์ธํธ๊ฐ scope ํ๋ผ๋ฏธํฐ๋ฅผ ์ง์ ํ์ง ์์๋ ์๋ ํฌํจ
- ๊ธฐ๋ณธ์ ์ธ ์ฌ์ฉ์ ์ ๋ณด๋ ๊ถํ์ ์ฌ์ฉ
- Optional
- ํด๋ผ์ด์ธํธ๊ฐ ๋ช ์์ ์ผ๋ก ์์ฒญํ ๋๋ง ํฌํจ๋๋ ์ค์ฝํ
- scope ํ๋ผ๋ฏธํฐ์ ํฌํจ์์ผ์ผ ํ ํฐ์ ํฌํจ๋จ
- ํด๋ผ์ด์ธํธ๊ฐ ํ์ํ ๋๋ง ์์ฒญ
- ์ถ๊ฐ์ ์ธ ๊ถํ์ด๋ ์ ๋ณด๊ฐ ํ์ํ ๋ ์ฌ์ฉ
์ค์ตํ๊ธฐ
๋ด ์ดํ๋ฆฌ์ผ์ด์ ์ Read, Write๊ถํ์ด ์๋ค๊ณ ๊ฐ์ ํ๊ณ , ๋ชจ๋ ์ฌ์ฉ์์๊ฒ๋ Read๋ฅผ ํ์ํ ์ฌ์ฉ์์๊ฒ๋ง Write๊ถํ์ ์ ๊ณตํ๋ค๊ณ ๊ฐ์ ํ์.
๋จผ์ Role์ ์ถ๊ฐํ๋ค.
- myapp:read ํ ์คํธ ์ดํ๋ฆฌ์ผ์ด์ ์ ์ฝ๊ธฐ๋ง ๊ฐ๋ฅํ Role์ด๋ผ๊ณ ๊ฐ์
- myapp:write ํ ์คํธ ์ดํ๋ฆฌ์ผ์ด์ ์ ์ฐ๊ธฐ๋ง ๊ฐ๋ฅํ Role์ด๋ผ๊ณ ๊ฐ์
๋ฐฉ๊ธ ์์ฑํ Role์ User์ ๋งคํํ๋ค.
์ง๊ธ์ ํ
์คํธ ํ๊ฒฝ์ด๋ ์ ์ ์ Role์ ๋งคํํ์ง๋ง, ํ๋ก๋์
ํ๊ฒฝ์์๋ Group์ ๋ง๋ค์ด ํด๋น ๊ทธ๋ฃน์ Role์ ๋งคํํ๋๊ฒ์ด ์ข๋ค.
์ด์ Client Scope๋ฅผ ์์ฑํ๋ค.
- myapp:read ํ ์คํธ ์ดํ๋ฆฌ์ผ์ด์ ์ ์ฝ๊ธฐ๋ง ๊ฐ๋ฅํ ๊ถํ๋ฒ์๋ผ๊ณ ๊ฐ์
- myapp:write ํ ์คํธ ์ดํ๋ฆฌ์ผ์ด์ ์ ์ฐ๊ธฐ๋ง ๊ฐ๋ฅํ ๊ถํ๋ฒ์๋ผ๊ณ ๊ฐ์
์ด์ Client Scope์ ๊ฐ๊ฐ ์ฌ๋ฐ๋ฅธ Role์ ๋งคํํ๋ค.
- myapp:read ํ ์คํธ ์ดํ๋ฆฌ์ผ์ด์ ์ ์ฝ๊ธฐ๋ง ๊ฐ๋ฅํ ๊ถํ๋ฒ์๋ผ๊ณ ๊ฐ์
- myapp:write ํ ์คํธ ์ดํ๋ฆฌ์ผ์ด์ ์ ์ฐ๊ธฐ๋ง ๊ฐ๋ฅํ ๊ถํ๋ฒ์๋ผ๊ณ ๊ฐ์
์ด์ Client(oauth-playground) -> Client Scopeํญ์์ ์์์ ์์ฑํ ๋๊ฐ ๋ชจ๋ ์ถ๊ฐํ๋ค.
์ด๋, read๋ Default๋ก, write๋ Optional๋ก ์ค์ ํ๋ค.
์ด์ http://localhost:8000/์์ Send Authorization Request ์์ฑ์ ์๋ก์ด ๊ถํ ํ์ฉ์ด ๋์ค๊ณ ,
ํ์ฉํ๋ฉด realm_access์ ์๋ก์ด myapp:read ์ญํ ์ด ์ถ๊ฐ๋๊ฒ์ ๋ณผ ์์๋ค.
์ด์ Write๊ถํ์ ์ถ๊ฐํ ์ ์๋๋ก ์์ฒญ์ ์์ฑํ๋ค.
Scope์ myapp:write๋ฅผ ๋ช
์์ ์ผ๋ก ์์ฑํ๊ณ , Send Authorization Request๋ฅผ ์คํํ๋ค.
realm_access์ ์๋ก์ด myapp:write ์ญํ ์ด ์ถ๊ฐ๋๊ฒ์ ํ์ธํ๋ค.
์ถ๊ฐ์ค๋ช ) Role์ User(or Group) ๊ทธ๋ฆฌ๊ณ Scope์ ๋ชจ๋ ํ ๋น๋์ด์์ด์ผํ๋ค.
Role์ User์ Scope์ ๋ชจ๋ ํ ๋น๋์ด์์ด์ผ ์ ์์ ์ผ๋ก ๋์ํ๋ค.
User -> Role -> Scope -> Client ์ ์ฐ๊ฒฐ ๊ณ ๋ฆฌ๊ฐ ๋ชจ๋ ์์ฑ๋์ด์ผ ์ ์์ ์ผ๋ก ์ ๊ทผ ์ ์ด๊ฐ ๊ฐ๋ฅํ๋ค.
ํ๋ก๋์
์์๋ User๋์ Group์ ์ฌ์ฉํ์!
- Client Scope์ Role ํ ๋น
- ๋ชฉ์ : ํด๋น ์ค์ฝํ๊ฐ ์ด๋ค ์ญํ ์ ํฌํจํ ์ ์๋์ง ์ ์
- ์ค์ ์์น: Client Scope > {scope-name} > Scope ํญ
- ์๋ฏธ: "์ด ์ค์ฝํ๋ ์ด๋ฌํ ์ญํ ๋ค์ ํฌํจํ ์ ์๋ค"
- User์ Role ํ ๋น
- ๋ชฉ์ : ์ฌ์ฉ์๊ฐ ์ค์ ๋ก ์ด๋ค ์ญํ ์ ๊ฐ์ง ์ ์๋์ง ์ ์
- ์ค์ ์์น: Users > {user-name} > Role Mappings
- ์๋ฏธ: "์ด ์ฌ์ฉ์๋ ์ด๋ฌํ ์ญํ ๋ค์ ๊ฐ์ง๋ค"
์ค์ต #6) Access Token ๊ฒ์ฆํ๊ธฐ
์ด์ Introspect ์๋ํฌ์ธํธ๋ฅผ ์ด์ฉํด์ Access Token์ ๊ฒ์ฆํด๋ณธ๋ค.
# http://localhost:8080/realms/myrealm/.well-known/openid-configuration ์์
# introspection_endpointํ์ธ
"introspection_endpoint": "http://localhost:8080/realms/myrealm/protocol/openid-connect/token/introspect"
Introspection_endpoint ์ดํดํ๊ธฐ
Token Introspection ์๋ํฌ์ธํธ๋ OAuth 2.0 ํ ํฐ์ ์ํ์ ๋ฉํ๋ฐ์ดํฐ๋ฅผ ํ์ธํ๋ ๋ฐ ์ฌ์ฉ๋๋ ํ์ค ์๋ํฌ์ธํธ์ด๋ค.
AccessToken์ ์ ํจ์ฑ์ ๊ฒ์ฆํ๊ณ , ํ ํฐ์ ํ์ฌ์ํ(active/inactive) ํ์ธ์ ํด๋น ์๋ํฌ์ธํธ์์ ์งํํ๋ค.
# ์์ฒญ์์
curl -X POST \
'http://localhost:8080/realms/myrealm/protocol/openid-connect/token/introspect' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Authorization: Basic {์ฌ๊ธฐ์_Base64_์ธ์ฝ๋ฉ๋_๊ฐ}' \
-d 'token={your_access_token}'
# ์๋ต์์
{
"active": true, // ํ ํฐ ์ ํจ์ฑ
"exp": 1678901234, // ๋ง๋ฃ ์๊ฐ
"sub": "user-id", // ์ฌ์ฉ์ ID
"username": "user01", // ์ฌ์ฉ์๋ช
"scope": "myapp:read myapp:write",// ์ค์ฝํ
"client_id": "oauth-playground", // ํด๋ผ์ด์ธํธ ID
"token_type": "Bearer" // ํ ํฐ ํ์
}
์ค์ตํ๊ธฐ
๋จผ์ Client๊ฐ Confidential๋ก ์ค์ ๋์ด์์ด์ผ ํ๋ค.
Client (oauth-playground) Settingํญ์์ Client authentication์ On์ผ๋ก ๋ณ๊ฒฝํ๋ค.
Service accounts roles๋ Enable ํ๋ค.
Saveํ๋ฉด Credentials ํญ์ด ์๊ธด๋ค.
Credentials ํญ์์ Client Secret ๊ฐ์ ๋ณต์ฌํ๋ค.
Access Token์ curl๋ก ๋ฐ์์ค์! (Credentials ์ค์ ์ผ๋ก ๋ณ๊ฒฝํ๋ฉด http://localhost:8000/์์๋ AccessToken๋ฐ๊ธ ์๋จ)
์๋ IrefKp5o3IJxKCBQXCsRYmLyWups8uVA ๋ถ๋ถ์ ์์์ ๋ณต์ฌํ Client Secret๊ฐ์ผ๋ก ๋ฐ๊ฟ์ผํ๋ค.
# Client Secret๊ฐ ๋ณ์๋ก ์ ์ฅ
export KEYCLOAK_CLIENT_SECRET=IrefKp5o3IJxKCBQXCsRYmLyWups8uVA # ์์์ ๋ณต์ฌํ ๊ฐ์ผ๋ก ๋ณ๊ฒฝ
# Access Token ์์ฒญ
export KEYCLOAK_ACCESS_TOKEN=$(curl -X POST \
'http://localhost:8080/realms/myrealm/protocol/openid-connect/token' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d "client_id=oauth-playground" \
-d "client_secret=${KEYCLOAK_CLIENT_SECRET}" \
-d 'grant_type=client_credentials' | jq -r '.access_token')
์ด์ ์๋ํฌ์ธํธ๋ก ์์ฒญ์ ๋ณด๋ด๋ณด์
# Base64 ์ธ์ฝ๋ฉ๋ Client Secret ์ ์ฅ
export KEYCLOAK_CLIENT_SECRET_ENCODING=$(echo -n "oauth-playground:${KEYCLOAK_CLIENT_SECRET}" | base64)
# ๊ฐ ๋ชจ๋ ๋ค์ด์๋์ง ์ ๊ฒ
echo "Client Secret: $KEYCLOAK_CLIENT_SECRET"
echo "Access Token: $KEYCLOAK_ACCESS_TOKEN"
echo "Encoded Client Secret: $KEYCLOAK_CLIENT_SECRET_ENCODING"
# Access Token ๊ฒ์ฆ
curl -X POST \
'http://localhost:8080/realms/myrealm/protocol/openid-connect/token/introspect' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H "Authorization: Basic $KEYCLOAK_CLIENT_SECRET_ENCODING" \
-d "token=$KEYCLOAK_ACCESS_TOKEN" | jq '.'
์ ๋ช
๋ น์ด๋ฅผ ์์๋๋ก ์คํํ๋ฉด,
์ด์ ๊ฐ์ ์๋ต์ ๋ฐ์ ์ ์๋ค.