์ด ํฌ์คํ ์์๋ ์์ธํ ๋ด์ฉ ๋ณด๋ค๋ ๊ฐ๋ ๋ง๋ณด๊ธฐ๐ ์์ค์ ๋ด์ฉ์ ๋๋ค.
๐ OAuth 2.0, OpenID Connect, SAML ๊ฐ๋จ ๊ฐ๋ ์ ๋ฆฌ
- OAuth 2.0: ๊ถํ ๋ถ์ฌ์ ์ค์ ์ ๋ ํ๋กํ ์ฝ
- OpenID Connect: OAuth 2.0์ ํ์ฅํ์ฌ ์ธ์ฆ ๋ ์ด์ด ์ถ๊ฐ
- SAML: ์ํฐํ๋ผ์ด์ฆ ํ๊ฒฝ์ ์ ํฉํ XML ๊ธฐ๋ฐ ์ธ์ฆ ํ๋กํ ์ฝ
๊ตฌ๋ถ | OAuth 2.0 | OpenID Connect | SAML |
๋ชฉ์ | ๊ถํ ๋ถ์ฌ(Authorization) | ์ฌ์ฉ์ ์ธ์ฆ(Authentication) | ์ฌ์ฉ์ ์ธ์ฆ(Authentication) |
๋ฐ์ดํฐ ํ์ | JSON | JSON | XML |
์ฌ์ฉ ์ฌ๋ก | API ์ธ์ฆ, ๋ชจ๋ฐ์ผ ์ฑ | ์์
๋ก๊ทธ์ธ, API ์ธ์ฆ ๋ชจ๋ฐ์ผ ์ฑ, SPA, API ๊ธฐ๋ฐ ์๋น์ค์ ์ ํฉ |
๊ธฐ์
SSO, ์น ์ ํ๋ฆฌ์ผ์ด์
์ธ์ฆ ์ ํต์ ์ธ ์น ์ ํ๋ฆฌ์ผ์ด์ ์ ์ ํฉ |
๋ณต์ก์ฑ | ์ค๊ฐ | ์ค๊ฐ REST API ํธ์ถ, JWT ์ฒ๋ฆฌ๋ก ๋น๊ต์ ๋จ์ |
๋์ XML ์ฒ๋ฆฌ, ๋ณต์กํ ๋ฆฌ๋ค์ด๋ ์ ๋ก์ง ํ์ |
๋ณด์ํน์ง | Access Token, Refresh Token ์ฌ์ฉ |
ID Token(JWT) ์ถ๊ฐ ์ฌ์ฉ | ๋์งํธ ์๋ช , ์ํธํ ์ง์ |
ํต์ ๋ฐฉ์ | Authorization Code, Access Token ๊ตํ |
[์๋ฒ ๊ฐ ์ง์ ํต์ ๊ฐ๋ฅ, API ๊ธฐ๋ฐ ํต์ ] Client ←→ Authorization Server - ์๋ฒ ๊ฐ ์ง์ ํต์ - REST/JSON ๊ธฐ๋ฐ ํต์ - Token ๊ธฐ๋ฐ ์ธ์ฆ |
[๋ธ๋ผ์ฐ์ ์ค์ฌ์ ๋ฆฌ๋๋ ์
๊ธฐ๋ฐ ํต์ ] SP ↔ Browser ↔ IdP - ๋ชจ๋ ํต์ ์ด ๋ธ๋ผ์ฐ์ ๋ฅผ ํตํด ์ด๋ฃจ์ด์ง - XML ๊ธฐ๋ฐ์ ๋ฉ์์ง - ๋ฆฌ๋ค์ด๋ ์ ์ ํตํ ํต์ |
ํ์คํ ๊ธฐ๊ตฌ | IETF | OpenID Foundation | OASIS |
๐ OAuth 2.0
OAuth 2.0 ์ ์ก์ธ์คํ ํฐ(AccessToken)์ ํตํด API๋ ๋ณดํธ๋ ๋ฆฌ์์ค์ ์ ๊ทผํ ์ ์๋๋ก ํ๋ "๊ถํ๋ถ์ฌ(Authorization)"๋ฅผ ์ํ ํ๋กํ ์ฝ์ด๋ค. (The OAuth 2.0 Authorization Framework)
- ์ฃผ์๊ฐ๋
๋ฆฌ์์ค ์์ ์ (Resource Owner) |
์์์ ๋ํ ๊ถํ์ ๊ฐ์ง ์ฌ์ฉ์ |
ํด๋ผ์ด์ธํธ (Client) |
์ฌ์ฉ์์ ์น์ธ์ ๋ฐ์ ๋ณดํธ๋ ์์์ ์ ๊ทผํ๋ ค๋ ์ ํ๋ฆฌ์ผ์ด์
์ฌ์ฉ์์ ๊ถํ์ ๋์ ๋ฐ์์ค๊ณ , ์ก์ธ์ค ํ ํฐ์ ๊ด๋ฆฌํจ |
๋ฆฌ์์ค ์๋ฒ (Resource Server) |
๋ณดํธ๋ ๋ฆฌ์์ค๋ฅผ ์ ๊ณตํ๋ ์๋ฒ |
์ธ์ฆ์๋ฒ (Authorization Server) |
ํด๋ผ์ด์ธํธ๊ฐ ์ฌ์ฉ์๋ฅผ ๋์ ํด ๋ฆฌ์์ค ์๋ฒ์ ์ ๊ทผํ ์ ์๋ ์ก์ธ์ค ํ ํฐ์ ๋ฐ๊ธํ๋ ์๋ฒ |
์ก์ธ์ค ํ ํฐ (Access Token) |
๋ฆฌ์์ค ์๋ฒ์ ๋ํ ์ ๊ทผ ๊ถํ์ ๋ํ๋ด๋ ํ ํฐ (ํด๋ผ์ด์ธํธ๊ฐ ๋ฆฌ์์ค ์๋ฒ์ ์์ฒญํ ๋ ์ฌ์ฉํจ) |
- ์ฃผ์ ๊ถํ ๋ถ์ฌ ์ ํ(Grant Types)
* ์ฐธ๊ณ :
- https://alexbilbie.github.io/guide-to-oauth-2-grants/
- https://oauth.net/2/grant-types/
- https://www.2e.co.kr/news/articleView.html?idxno=208594
Authorization Code Grant ์น์ธ ์ฝ๋ ์ ํ |
์ฃผ๋ก ์๋ฒ๊ฐ ํต์ ์์ ์ฌ์ฉ๋๋ค. ์ฌ์ฉ์๊ฐ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ก๊ทธ์ธํ ํ ์ธ์ฆ ์๋ฒ๋ Authorization Code๋ฅผ ํด๋ผ์ด์ธํธ์๊ฒ ์ ์กํ๋ค. ํด๋ผ์ด์ธํธ๋ ์ด ์ฝ๋๋ฅผ ์ฌ์ฉํด ์ธ์ฆ ์๋ฒ๋ก๋ถํฐ Access Token์ ์์ฒญํ๋ค. ๋ณด์์ฑ์ด ๋์ (Access Token์ด ๋ธ๋ผ์ฐ์ ๋ฅผ ํตํด ๋ ธ์ถ๋์ง ์์). OpenID Connect๋ ์ด ์ค ์ฃผ๋ก Authorization Code Grant๋ฅผ ํ์ฅํ์ฌ ์ฌ์ฉํ๋ค. |
Client Credentials Grant ์ฑ์ธ์ฆ ์ ํ |
์ฌ์ฉ์์์ด ์ดํ๋ฆฌ์ผ์ด์
์์ฒด์ ์ธ์ฆ๋ง ํ์ํ ๊ฒฝ์ฐ (e.g. ์ดํ๋ฆฌ์ผ์ด์
์ด ๋ฆฌ์์ค ์ค๋์ธ ๊ฒฝ์ฐ)์ฌ์ฉํ๋ฉฐ, ์ฃผ๋ก ๋ฐฑ์๋ ์์คํ ๊ฐ์ API ํธ์ถ๊ณผ ๊ฐ์ ์๋ฒ ๊ฐ ํต์ ์์ ์ฌ์ฉ๋๋ค. ํด๋ผ์ด์ธํธ ์ ํ๋ฆฌ์ผ์ด์ ์ด ์์ฒด ์๊ฒฉ ์ฆ๋ช ์ ์ฌ์ฉํ์ฌ ์ธ์ฆ ์๋ฒ์์ Access Token์ ์์ฒญํ๋ค. |
Device Authorization Grant ๋๋ฐ์ด์ค ์ธ์ฆ ์ ํ |
๋์คํ๋ ์ด๋ ํค๋ณด๋๊ฐ ์๋ ๋๋ฐ์ด์ค(์: ์ค๋งํธ TV, IoT ๊ธฐ๊ธฐ ๋ฑ)๊ฐ ์ฌ์ฉ์ ์ธ์ฆ์ ์ฒ๋ฆฌํด์ผ ํ ๋ ์ฌ์ฉํ๋ค. ๋๋ฐ์ด์ค๋ ์ฌ์ฉ์์๊ฒ ๋ณ๋์ ์ธ์ฆ ํ์ด์ง(URL)์ ์ธ์ฆ ์ฝ๋๋ฅผ ์ ๊ณตํ๊ณ , ์ฌ์ฉ์๋ ์ด ์ ๋ณด๋ฅผ ๋ธ๋ผ์ฐ์ ๋ ์ค๋งํธํฐ์์ ์ ๋ ฅํ์ฌ ์ธ์ฆ์ ์๋ฃํ๋ค. ์) ์ค๋งํธ TV์์ ์ ํ๋ธ ์ฑ์ ๋ก๊ทธ์ธํ๊ณ ์ถ์ ๋, TV ํ๋ฉด์ "https://example.com/device"์ ์ธ์ฆ ์ฝ๋๋ฅผ ํ์ํจ |
Implicit Grant ์๋ฌต์ ์ ํ (์ฌ์ฉ โโโ) |
์ฃผ๋ก SPA(Single Page Application) ์์ ์ฌ์ฉ๋๋ค. ํด๋ผ์ด์ธํธ๋ Authorization Code๋ฅผ ์์ฒญํ๋ ๋์ , ์ฌ์ฉ์๊ฐ ๋ก๊ทธ์ธํ๋ฉด ๋ฐ๋ก Access Token์ ๋ฐ๋๋ค. ์ธ์ฆ ๊ณผ์ ์ด ๊ฐ๋จํ์ง๋ง Access Token์ด URL๋ก ๋ ธ์ถ๋์ด ๋ณด์์ฑ์ด ๋ฎ์ ์ฌ์ฉํ์ง ์๋๋ค. |
Resource Owner Password Credentials Grant ์ฌ์ฉ์ ๋น๋ฐ๋ฒํธ ์ธ์ฆ ์ ํ (์ฌ์ฉ โโโ) |
์ฌ์ฉ์๊ฐ ์ฑ ์์ผ๋ก ID/๋น๋ฐ๋ฒํธ๋ก ๋ก๊ทธ์ธํ์ฌ ์ธ์ฆํ๊ณ ๊ถํ ๋ถ์ฌ์ ๋์ํ๋ฉด ์ ๊ทผ ํ ํฐ์ ๋ฐ๊ธํ๋ ์ ํ์ด๋ค. ์ฌ์ฉ์ ์๊ฒฉ ์ฆ๋ช ์ด ํด๋ผ์ด์ธํธ์๊ฒ ๋ ธ์ถ๋๊ธฐ ๋๋ฌธ์ ๋ณด์ ์ํ์ด ๋์ ์์ฌ ์๋น์ค๋ ๋งค์ฐ ์ ๋ขฐํ ์ ์๋ ์ ํ๋ฆฌ์ผ์ด์ ์์๋ง ์ ํ์ ์ผ๋ก ์ฌ์ฉํ๋ค. |
- OAuth 2.0 ๊ธฐ๋ณธ ํ๋ฆ
- (A): ํด๋ผ์ด์ธํธ(Client)๊ฐ ๋ฆฌ์์ค ์์ ์(Resource Owner)์๊ฒ ์ธ์ฆ ์์ฒญ์ ๋ณด๋
- (B): ๋ฆฌ์์ค ์์ ์(Resource Owner)๊ฐ ํด๋ผ์ด์ธํธ(Client)์๊ฒ ์ธ์ฆ ์น์ธ ๋ถ์ฌ (Authorization Grant).
- (C): ํด๋ผ์ด์ธํธ(Client)๋ ์ด ์ธ์ฆ ์น์ธ์ ๊ถํ ์๋ฒ(Authorization Server)์ ์ ๋ฌํ์ฌ ์ก์ธ์ค ํ ํฐ ์์ฒญ
- (D): ๊ถํ ์๋ฒ๋(Authorization Server) ํด๋ผ์ด์ธํธ(Client)์๊ฒ ์ก์ธ์ค ํ ํฐ ๋ฐํ
- (E): ํด๋ผ์ด์ธํธ(Client)๋ ๋ฆฌ์์ค ์๋ฒ(Resource Server)์ ์ก์ธ์ค ํ ํฐ์ ์ฌ์ฉํ์ฌ ๋ณดํธ๋ ๋ฆฌ์์ค์ ์ ๊ทผ
- (F): ๋ฆฌ์์ค ์๋ฒ๊ฐ ์์ฒญ๋ ๋ณดํธ๋ ๋ฆฌ์์ค๋ฅผ ํด๋ผ์ด์ธํธ์๊ฒ ๋ฐํ
- OAuth 2.0 ์ธ์ฆ ์์ (์ฒ ์์ ๊ตฌ๊ธ๋๋ผ์ด๋ธ)
+-----------+
| Client | +------------------+
| (ํฌํ ๋ถ ์ฑ) | ----(A) Authorization Request----> | Resource Owner |
| | <----(B) Authorization Grant----- | (์ฒ ์) |
| | +------------------+
| |
| | +------------------+
| | ----(C) Authorization Grant-----> | Authorization |
| | <----(D) Access Token----------- | Server |
| | | (๊ตฌ๊ธ OAuth) |
| | +------------------+
| |
| | +------------------+
| | ----(E) Access Token------------> | Resource Server |
| | <----(F) Protected Resource------ | (๊ตฌ๊ธ ๋๋ผ์ด๋ธ) |
+-----------+ +------------------+
- (A) Authorization Request
- ํฌํ ๋ถ ์ฑ์ ์ฒ ์์๊ฒ "๊ตฌ๊ธ ๋๋ผ์ด๋ธ์ ์ฌ์ง์ ๊ฐ์ ธ์๋ ๋๊ฒ ์ต๋๊น?"๋ผ๋ ์์ฒญ์ ๋ณด๋ธ๋ค.
- ์ฒ ์๋ "ํ์ฉ" ๋ฒํผ์ ํด๋ฆญํ๋ค.
- (B) Authorization Grant
- ์ฒ ์๊ฐ "ํ์ฉ"์ ํด๋ฆญํ๋ฉด, ๊ตฌ๊ธ OAuth ์๋ฒ๋ ํฌํ ๋ถ ์ฑ์ ์ธ์ฆ ์ฝ๋๋ฅผ ๋ฐํํ๋ค.
- ์ธ์ฆ ์ฝ๋๋ ์ฒ ์๊ฐ ์ ๊ทผ์ ํ์ฉํ๋ค๋ ์ฆ๋ช ์ด๋ค.
- (C) Authorization Grant
- ํฌํ ๋ถ ์ฑ์ ์ด ์ธ์ฆ ์ฝ๋๋ฅผ ๊ตฌ๊ธ OAuth ์๋ฒ์ ๋ณด๋ด๋ฉฐ, "์ก์ธ์ค ํ ํฐ์ ์ฃผ์ธ์!"๋ผ๊ณ ์์ฒญํ๋ค.
- (D) Access Token
- ๊ตฌ๊ธ OAuth ์๋ฒ๋ ์ธ์ฆ ์ฝ๋๋ฅผ ๊ฒ์ฆํ ํ, ํฌํ ๋ถ ์ฑ์ "์ก์ธ์ค ํ ํฐ"์ ๋ฐ๊ธํ๋ค.
- ์ด ํ ํฐ์ ํฌํ ๋ถ ์ฑ์ด ๊ตฌ๊ธ ๋๋ผ์ด๋ธ์ ์ ๊ทผํ ์ ์๋ ๊ถํ์ ๋ถ์ฌํ๋ค.
- (E) Access Token
- ํฌํ ๋ถ ์ฑ์ ๊ตฌ๊ธ ๋๋ผ์ด๋ธ ์๋ฒ์ ์ก์ธ์ค ํ ํฐ์ ํฌํจํ ์์ฒญ์ ๋ณด๋ธ๋ค.
- "์ด ํ ํฐ์ผ๋ก ์ฒ ์์ ์ฌ์ง์ ๊ฐ์ ธ์ค๊ฒ ์ต๋๋ค!"๋ผ๋ ์๋ฏธ์ด๋ค.
- (F) Protected Resource
- ๊ตฌ๊ธ ๋๋ผ์ด๋ธ ์๋ฒ๋ ์ก์ธ์ค ํ ํฐ์ ์ ํจ์ฑ์ ํ์ธํ ํ, ์ฒ ์์ ์ฌ์ง ํ์ผ์ ํฌํ ๋ถ ์ฑ์ ๋ฐํํ๋ค.
- OAuth 2.0 Confidential Client / Public Client ๊ฐ๋ ์ดํด
[์ฐธ๊ณ ] What's the difference between Confidential and Public clients? - OAuth in Five Minutes
OAuth 2.0์์ ํด๋ผ์ด์ธํธ๋ฅผ "๊ธฐ๋ฐ ํด๋ผ์ด์ธํธ"์ "๊ณต์ฉ ํด๋ผ์ด์ธํธ"๋ก ๋๋๋ ์ด์ ๋ ๋ณด์ ์์ค์ด ๊ฐ๊ธฐ ๋ค๋ฅด๊ธฐ ๋๋ฌธ์ด๋ฉฐ,
๋ณด์์ด ์ทจ์ฝํ ํ๊ฒฝ์์๋ ๊ณต์ฉ ํด๋ผ์ด์ธํธ์ ํ ํฐ์ด ์ฝ๊ฒ ๋
ธ์ถ๋ ์ ์๊ธฐ ๋๋ฌธ์ ๋ณ๋์ ๋ณด์ ๊ฐํ ์ฅ์น๊ฐ ํ์ํ๋ค.
- ๊ธฐ๋ฐ ํด๋ผ์ด์ธํธ(Confidential Client)
- ํด๋ผ์ด์ธํธ๊ฐ ๋ณด์๋ ํ๊ฒฝ์์ ์คํ๋๋ฏ๋ก, ๋ฏผ๊ฐํ ์ ๋ณด๋ฅผ ์์ ํ๊ฒ ๋ณด๊ดํ ์ ์๋ ํด๋ผ์ด์ธํธ
- ์์. ์ผํ ์ฑ์ ๋ฐฑ์๋ ์๋ฒ (.env, $key=)
- ๋ณดํต ์ธ๋ถ์๋ ์ฐ๊ฒฐ๋์ง ์๊ณ , ์์ ์ ์๋ฒ ๋ด๋ถ์์ ๋ฏผ๊ฐํ ์ ๋ณด(ํด๋ผ์ด์ธํธ ๋น๋ฐ, ํ ํฐ ๋ฑ)๋ฅผ ์์ ํ๊ฒ ๋ณด๊ดํ๋ ํด๋ผ์ด์ธํธ๋ฅผ ๋งํ๋ค.
- ๊ณต์ฉ ํด๋ผ์ด์ธํธ(Public Client)
- ํด๋ผ์ด์ธํธ๊ฐ ๋ณด์๋์ง ์์ ํ๊ฒฝ์์ ์คํ๋๋ฏ๋ก, ๋ฏผ๊ฐํ ์ ๋ณด๋ฅผ ์์ ํ๊ฒ ๋ณด๊ดํ ์ ์๋ ํด๋ผ์ด์ธํธ
- ์์. ๋ชจ๋ฐ์ผ ์ฑ ๋๋ ์น ๋ธ๋ผ์ฐ์ SPA
- ๊ฐ์ธ์ ์ค๋งํธํฐ์ด๋ PC์์ ์คํ๋๋๋ฐ, ๋์ปดํ์ผ๋๊ฑฐ๋ ๋คํธ์ํฌ์์ ๋ฐ์ดํฐ๊ฐ ํ์ทจ๋ ๊ฐ๋ฅ์ฑ์ด ์๋ค.
์ฆ, ๊ณต์ฉ ํด๋ผ์ด์ธํธ์ ๊ฒฝ์ฐ PKCE(Proof Key for Code Exchange) ๊ฐ์ ๋ณด์ ๋ฐฉ์์ ํตํด ์ธ์ฆ ์ฝ๋๊ฐ ํ์ทจ๋๋๋ผ๋ ๊ณต๊ฒฉ์๊ฐ ์ฌ์ฉํ ์ ์๋๋ก ๋ณดํธํด์ผํ๋ค.
- PKCE (Proof Key for Code Exchange) ๋? ( ํฝ์(Pixy)- ๋ผ๊ณ ์ฝ๋๋ค ๐)
๊ณต์ฉ ํด๋ผ์ด์ธํธ์ ๊ฒฝ์ฐ ์ธ์ฆ ์ฝ๋๋ฅผ ํ์ทจ๋นํ๋ฉด, ์ด๋ฅผ ๊ณต๊ฒฉ์๊ฐ ๋ค๋ฅธ ํด๋ผ์ด์ธํธ์์ ์ฌ์ฌ์ฉํ ์ํ์ด ์๋ค.
PKCE๋ ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํ ๋ณด์ ๊ฐํ ๋ฐฉ๋ฒ์ด๋ค.
- ์๋ ์๋ฆฌ:
PKCE๋ ์ธ์ฆ ์์ฒญ๊ณผ ์ธ์ฆ ์ฝ๋ ๊ตํ ์, ์ฝ๋ ๊ฒ์ฆ ๊ฐ (code_challenge/code_verifier) ์ ์ถ๊ฐํ์ฌ ์ธ์ฆ ์ฝ๋์ ์์ ์ฑ์ ๋ณด์ฅํ๋ค.- code_verifier (์๋ณธ ๊ฐ)
- code_challenge (verifier์ ํด์๊ฐ/๋จ๋ฐฉํฅ ํด์๋ก, ์ญ์ฐ์ด ๋ถ๊ฐ๋ฅ)
- PKCE๋ฅผ ์ฌ์ฉํ์ง ์๋ ์๋๋ฆฌ์ค
- ์ ์ ์ฑ: ์ธ์ฆ ์์ฒญ (client_id + redirect_uri)
- ์ ์ฑ ์ฑ: ์ด ์์ฒญ์ ๊ฐ๋ก์ฑ
- ์ ์ฑ ์ฑ: ๊ฐ๋ก์ฑ ์ ๋ณด๋ก ์ธ์ฆ ์๋ฒ์ ์ ๊ทผ
- ์ธ์ฆ ์๋ฒ: ์ฝ๋ ๋ฐ๊ธ
- ์ ์ฑ ์ฑ: ๋ฐ๊ธ๋ฐ์ ์ฝ๋๋ก ์ก์ธ์ค ํ ํฐ ํ๋ ๐
- PKCE๋ฅผ ์ฌ์ฉํ๋ ์๋๋ฆฌ์ค
- ์ ์ ์ฑ: ๋๋คํ code_verifier ์์ฑ (์: "abc123xyz789")
- ์ ์ ์ฑ: code_verifier๋ฅผ ํด์ํ์ฌ code_challenge ์์ฑ
- ์ ์ ์ฑ: ์ธ์ฆ ์์ฒญ ์ code_challenge ํฌํจํด์ ์ ์ก
- ์ธ์ฆ ์๋ฒ: code_challenge ์ ์ฅ
- ์ ์ ์ฑ: ์ก์ธ์ค ํ ํฐ ์์ฒญ ์ ์๋ณธ code_verifier ์ ์ก
- ์ธ์ฆ ์๋ฒ: code_verifier๋ฅผ ํด์ํด์ ์ ์ฅ๋ challenge์ ๋น๊ต
- PKCE๊ฐ ํฌํจ๋ ๋ค์ด์ด๊ทธ๋จ
- ํด๋ผ์ด์ธํธ๊ฐ ๋๋คํ code_verifier ์์ฑ
- code_verifier๋ฅผ SHA256์ผ๋ก ํด์ํ์ฌ code_challenge ์์ฑ
- ์ธ์ฆ ์์ฒญ ์ code_challenge ํฌํจํ์ฌ ์ ์ก
- ์ธ์ฆ ์๋ฒ๋ code_challenge ์ ์ฅ
- ์ธ์ฆ ์ฝ๋๋ฅผ ํด๋ผ์ด์ธํธ์๊ฒ ๋ฐํ
- ํด๋ผ์ด์ธํธ๋ ํ ํฐ ์์ฒญ ์ ์๋ณธ code_verifier ์ ์ก
- ์๋ฒ๋ ๋ฐ์ verifier๋ก challenge๋ฅผ ์์ฑํ์ฌ ์ ์ฅ๋ ๊ฐ๊ณผ ๋น๊ต
- ๊ฒ์ฆ ์ฑ๊ณต ์ ์ก์ธ์ค ํ ํฐ ๋ฐ๊ธ
- ํด๋ผ์ด์ธํธ๋ ์ก์ธ์ค ํ ํฐ์ผ๋ก API ์์ฒญ
- ๋ณดํธ๋ ๋ฆฌ์์ค ์๋ต
+--------+ +-----------+ +-----------+
| | | | | |
| Client | | Auth | | Resource |
| | | Server | | Server |
+--------+ +-----------+ +-----------+
| | |
| 1. Generate verifier | |
| code_verifier="abc123" | |
| | |
| 2. Generate challenge | |
| challenge=hash(verifier) | |
| | |
| 3. Auth Request | |
|--------------------------->| |
| client_id, challenge | |
| | |
| | 4. Store challenge |
| | Show auth screen |
| | |
| 5. Auth Response | |
|<---------------------------| |
| auth_code | |
| | |
| 6. Token Request | |
|--------------------------->| |
| auth_code, verifier | |
| | |
| | 7. Verify: |
| | hash(verifier) == challenge|
| | |
| 8. Token Response | |
|<---------------------------| |
| access_token | |
| | |
| 9. API Request | |
|------------------------------------------------->| |
| Bearer access_token | |
| | |
| 10. Resource Response | |
|<-------------------------------------------------| |
| Data | |
| | |
- Bearer Tokens ๐ซ (์ ๋ฌ ๋ฐฉ์)
OAuth2.0์ ์ ๊ทผํ ํฐ ์ ํ์ด๋ ์ฌ์ฉ๋ฐฉ๋ฒ์ ๋ํด ์ ์๋์ด์์ง ์์ผ๋ฉฐ,
Bearer Token์ ํ์ฌ ๊ฐ์ฅ ์ผ๋ฐ์ ์ผ๋ก ์ฌ์ฉ๋๋ ์ ๊ทผํ ๊ทผ์ ํ์ด๋ค.
Bearer Token์ "์์ง์ ํ ํฐ"์ด๋ผ๊ณ ๋ ํ๋ค. (ํ๊ธ์ฒ๋ผ, ๋๊ตฌ๋ ์ด ํ ํฐ์ ๊ฐ์ง๊ณ ์์ผ๋ฉด ์ฌ์ฉํ ์ ์๋ค)
// Bearer Token ์ฌ์ฉ ์์
fetch('https://api.example.com/data', {
headers: {
'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIs...'
}
});
- ํน์ง
- ๋จ์ํ ํ ํฐ์ ์ ์ํ๋ ๊ฒ๋ง์ผ๋ก ์ธ์ฆ๋๋ค. ํ์ทจ๋๋ฉด ๋๊ตฌ๋ ์ฌ์ฉ ๊ฐ๋ฅํ๋ค. (์ํ!)
- ์ผ๋ฐ์ ์ผ๋ก JWT ํํ๋ก ๊ตฌํ
- Authorization Header๋ฅผ ํตํด ์ ์ก๋๋ฉฐ, ์ธ์ฝ๋ฉ์ฒ๋ฆฌ๋ ํ์ด๋ก๋(body) ๋๋ ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ๋ก ์ ๋ฌ๋๋ค.
์ฃผ์ํ ์ ์, Bearerํ ํฐ์ ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ๋ก ์ ์กํ๋๊ฒ์ ๊ทธ ์์ฒด๋ก ๋ณด์์ทจ์ฝ์ ์ด๊ธฐ๋๋ฌธ์ ์ฌ์ฉํ์ง ์์์ผํ๋ค.
- Token Introspection ๐
ํ ํฐ์ ํ์ฌ ์ํ์ ์ ํจ์ฑ์ ํ์ธํ๋ ํ๋ก์ธ์ค๋ฅผ ๋งํ๋ค.
// Token Introspection ์์ฒญ ์์
const response = await fetch('https://auth-server.com/introspect', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: 'token=eyJhbGciOiJIUzI1NiIs...'
});
// ์๋ต ์์
{
"active": true,
"client_id": "123456",
"username": "jsmith",
"scope": "read write",
"exp": 1589391341
}
- ํน์ง
- ํ ํฐ์ด ์ ํจํ์ง ์ค์๊ฐ ํ์ธ ๊ฐ๋ฅํ๋ค,
- ํ ํฐ์ ์์ธ ์ ๋ณด ์กฐํ ๊ฐ๋ฅํ๋ค.
- ์ค์ ์ง์ค์ ํ ํฐ ๊ด๋ฆฌ ๊ฐ๋ฅํ๋ค.
- Token Revocation ๐ซ
๋ฐ๊ธ๋ ํ ํฐ์ ๋ฌดํจํํ๋ ํ๋ก์ธ์ค๋ฅผ ๋งํ๋ค.
์ฌ์ฉ์ ๋ก๊ทธ์์, ๋ณด์ ์นจํด ๋ฐ์, ๊ถํ ๋ณ๊ฒฝ, ๋น๋ฐ๋ฒํธ ๋ณ๊ฒฝ ๋ฑ์ ์ํฉ์์ ์ฌ์ฉํ ์ ์๋ค.
// Token Revocation ์์ฒญ ์์
fetch('https://auth-server.com/revoke', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: 'token=eyJhbGciOiJIUzI1NiIs...'
});
- Bearer Tokens / Token Introspection/ Token Revocation ๊ฐ๋ ๊ด๊ณ ๋ฐ ์ฝ๋์ํ
[Bearer Token]
โ
โโโ> [Token Introspection]
โ - ํ ํฐ ์ ํจ์ฑ ํ์ธ
โ - ํ ํฐ ์ ๋ณด ์กฐํ
โ
โโโ> [Token Revocation]
- ํ ํฐ ๋ฌดํจํ
- ์ ๊ทผ ๊ถํ ํ์
class TokenService {
// Bearer ํ ํฐ ์์ฑ
async createBearerToken(user) {
return jwt.sign({ userId: user.id }, 'secret', { expiresIn: '1h' });
}
// ํ ํฐ ๊ฒ์ฌ (Introspection)
async introspectToken(token) {
try {
const decoded = jwt.verify(token, 'secret');
const isRevoked = await this.checkIfTokenRevoked(token);
return {
active: !isRevoked,
...decoded
};
} catch (error) {
return { active: false };
}
}
// ํ ํฐ ํ๊ธฐ (Revocation)
async revokeToken(token) {
await redis.set(`revoked:${token}`, 'true');
await redis.expire(`revoked:${token}`, 24 * 60 * 60); // 24์๊ฐ ์ ์ง
}
}
๐ OpenID Connect
์ฌ์ฉ์ ์ธ์ฆ(Authentication)์ ์ํ ํ๋กํ ์ฝ๋ก ์์๋์์ผ๋ ํ์ฌ๋ OAuth 2.0์ ๊ธฐ๋ฐ์ผ๋ก ํ ์ฌ์ฉ์ ์ธ์ฆ ํ๋กํ ์ฝ์ด๋ค.
OpenID Connect๋, OAuth 2.0 ํ๋กํ ์ฝ ์์ ๊ตฌ์ถ๋ Identity Layer์ด๋ค.
OAuth 2.0์ด ๊ถํ ๋ถ์ฌ(Authorization)์ ์ค์ ์ ๋๋ค๋ฉด, OpenID Connect๋ ์ธ์ฆ(Authentication)์ ์ถ๊ฐํ ํ๋กํ ์ฝ์ด๋ค.
OpenID Connect๋ OAuth2.0์ Authorization Code Grant (์น์ธ์ฝ๋์ ํ)์ ์ฌ์ฉํ๋ค.
๋ง์ ๋ํ ์๋น์ค ์ ๊ณต์ ์ฒด๋ค์ด OIDC Provider(IdP)๋ก ํ์ฉ๋๊ณ ์๋ค.
- ์ฃผ์ OIDC Provider
- Microsoft Azure AD
- Okta
- AWS Cognito
- GitHub
์ต์ข
์ฌ์ฉ์ (End User) |
์ค์ ์ธ์ฆ์ ์ํํ๋ ์ฌ์ฉ์ |
Relying Party (RP) |
OpenID Provider๋ฅผ ์ ๋ขฐํ๊ณ ์ฌ์ฉ์ ์ธ์ฆ์ ์์ฒญํ๋ ์ ํ๋ฆฌ์ผ์ด์ (์, ๋ก๊ทธ์ธํ์ด์ง) |
OpenID Provider (OP) |
์ฌ์ฉ์์ ์ ์์ ์ฆ๋ช ํ๊ณ ID Token์ ๋ฐ๊ธํ๋ ์๋ฒ (์: Google, Keycloak) |
ID Token | ์ฌ์ฉ์์ ์ ์ ์ ๋ณด๋ฅผ ํฌํจํ๋ JWT(JSON Web Token) ํ์์ ํ ํฐ |
UserInfo Endpoint |
์ฌ์ฉ์ ํ๋กํ ์ ๋ณด๋ฅผ ์ ๊ณตํ๋ ํ์คํ๋ REST API |
- ์ธ์ฆ ํ๋ฆ ์ ํ
* ์ฐธ๊ณ : https://backstage.forgerock.com/docs/am/6/oidc1-guide/
Authorization Code Flow ๊ธฐ๋ณธ ์ธ์ฆ ํ๋ฆ |
๊ฐ์ฅ ์ผ๋ฐ์ ์ด๊ณ ์์ ํ ๋ฐฉ์ ์๋ฒ ์ฌ์ด๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ ํฉ ID Token๊ณผ Access Token ๋ชจ๋ ๋ฐฑ์๋์์ ์์ ํ๊ฒ ์ฒ๋ฆฌ |
Implicit Flow ์๋ฌต์ ํ๋ฆ (์ฌ์ฉ โโโ) |
SPA์ ๊ฐ์ ํด๋ผ์ด์ธํธ ์ฌ์ด๋ ์ ํ๋ฆฌ์ผ์ด์
์ ์ ํฉ ๋ณด์์ฑ์ด ๋ค์ ๋ฎ์ ๋ฆฌ๋ค์ด๋ ํธ๋ฅผ ํตํด ์ง์ ID Token์ ๋ฐ์ |
Hybrid Flow ํ์ด๋ธ๋ฆฌ๋ ํ๋ฆ |
Authorization Code Flow์ Implicit Flow์ ์กฐํฉ ํ๋ก ํธ์๋์ ๋ฐฑ์๋ ๋ชจ๋์์ ํ ํฐ์ด ํ์ํ ๊ฒฝ์ฐ ์ฌ์ฉ ์ผ๋ถ ํ ํฐ์ ํ๋ก ํธ์๋๋ก, ์ผ๋ถ๋ ๋ฐฑ์๋๋ก ์ ๋ฌ |
- OpenID Connect์ Authorization Code Flow ๋ค์ด์ด๊ทธ๋จ
+---------+ +--------+ +-----------+
| | | | | |
| EndUser | | RP | | OP |
| | | | |(KeyCloak) |
+---------+ +--------+ +-----------+
| | |
| 1. ์๋น์ค ์ ๊ทผ | |
|--------------------->| |
| | |
| | 2. Auth Request |
| |--------------------->|
| | scope=openid profile |
| | response_type=code |
| | client_id, state |
| | |
| 3. ์ธ์ฆ ํ๋ฉด | |
|<--------------------------------------------|
| | |
| 4. ์ธ์ฆ์ ๋ณด ์ ๊ณต | |
|-------------------------------------------->|
| (id/password) | |
| | |
| | 5. Auth Code |
| |<---------------------|
| | code=xyz789 |
| | |
| | 6. Token Request |
| |--------------------->|
| | code, client_creds |
| | |
| | 7. Token Response |
| |<---------------------|
| | id_token |
| | access_token |
| | |
| | 8. UserInfo Request |
| |--------------------->|
| | Bearer token |
| | |
| | 9. UserInfo Response |
| |<---------------------|
| | {sub,name,email,...} |
| | |
- EndUser -> RP
- ์ฌ์ฉ์๊ฐ ์๋น์ค(RP) ์ ๊ทผ
- ๋ก๊ทธ์ธ ํ์
- RP -> OP
- ์ธ์ฆ ์์ฒญ ์์
- OpenID scope ์ง์
- OP -> EndUser : ๋ก๊ทธ์ธ ํ๋ฉด ํ์
- EndUser -> OP
- ์ฌ์ฉ์ ์ธ์ฆ ์ ๋ณด ์ ๊ณต
- ๊ถํ ๋์
- OP -> RP : ์ธ์ฆ ์ฝ๋ ์ ๋ฌ
- RP -> OP : ์ฝ๋๋ก ํ ํฐ ์์ฒญ
- OP -> RP
- ID Token
- Access Token
- RP -> OP : UserInfo ์์ฒญ
- OP -> RP : ์ฌ์ฉ์ ์ ๋ณด ์ ๋ฌ
- OAuth 2.0๊ณผ OIDC์ ์ฃผ์ ์ฐจ์ด์ (scope ๐)
ํด๋ผ์ด์ธํธ ์ด๊ธฐ ์์ฒญ์ scope ๊ฐ ์ถ๊ฐ๋๋ค.
OIDC์ Scope๋ ํด๋ผ์ด์ธํธ๊ฐ ์์ฒญํ ์ ์๋ ์ฌ์ฉ์ ์ ๋ณด์ ๋ฒ์๋ฅผ ์ ์ํ๊ณ ,
๊ฐ Scope๋ ํน์ ์ฌ์ฉ์ ์ ๋ณด ์งํฉ์ ๋ํ ์ ๊ทผ ๊ถํ์ ๋ํ๋ธ๋ค.
Scope๋ ์ฌ์ฉ์ ์ ๋ณด์ ๋ํ ํ์คํ๋ ์ ๊ทผ ๋ฐฉ์์ ์ ๊ณตํ๋ฉฐ, ํด๋ผ์ด์ธํธ๋ ํ์ํ ์ ๋ณด๋ง ์์ฒญํ๊ณ ๋ฐ์ ์ ์๋ค.
// OAuth 2.0 ์๋ต
{
"access_token": "eyJ0eXAi...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "8xLOxBtZp8..."
}
// OIDC ์๋ต (OAuth 2.0 + α)
{
"access_token": "eyJ0eXAi...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "8xLOxBtZp8...",
"id_token": "eyJ0eXAiOi...", // ์ถ๊ฐ๋จ!
"scope": "openid profile email" // ํน๋ณํ scope
}
Scope ์ฌ์ฉ์์ 1.
// Google ๋ก๊ทธ์ธ ์์ฒญ ์์
const googleAuth = {
scope: 'openid profile email', // ๊ธฐ๋ณธ ์ฌ์ฉ์ ์ ๋ณด๋ง ์์ฒญ
// ...
};
// ๋ฐ์ ์ ๋ณด๋ก ํ์๊ฐ์
/๋ก๊ทธ์ธ ์ฒ๋ฆฌ
async function handleGoogleLogin(idToken) {
const decoded = jwt.decode(idToken);
await createOrUpdateUser({
email: decoded.email,
name: decoded.name,
picture: decoded.picture
});
}
Scope ์ฌ์ฉ์์ 2.
// ๋ฐฐ์ก ์๋น์ค์ ๊ฒฝ์ฐ
const shippingAuth = {
scope: 'openid profile email address phone', // ๋ฐฐ์ก์ ํ์ํ ๋ชจ๋ ์ ๋ณด ์์ฒญ
// ...
};
// ๋ฐ์ ์ ๋ณด๋ก ๋ฐฐ์ก ์ฒ๋ฆฌ
async function handleShippingInfo(userInfo) {
await createShippingOrder({
recipient: userInfo.name,
address: userInfo.address,
phone: userInfo.phone_number
});
}
- ID Token JWT(JSON Web Token)
๊ฐ ํด๋ ์์ ํ ํฐ์ ์ ํจ์ฑ ๊ฒ์ฆ (iss, aud, exp, iat), ์ฌ์ฉ์ ์๋ณ (sub), ์ฌ์ฉ์ ์ ๋ณด ํ์ (name, email) ๋ชฉ์ ์ผ๋ก ์ฌ์ฉํ๋ค.
- ์ฌ์ฉ๋ชฉ์
- ์ธ์ฆ(Authentication) ๋ชฉ์
- ์ฌ์ฉ์ ์ ์ ์ฆ๋ช
- RP๊ฐ ๋ฐ๋์ ๊ฒ์ฆํด์ผ ํจ
- ์ฌ์ฉ๋ฒ์
- RP์ OP ์ฌ์ด์์๋ง ์ฌ์ฉ
- ํ์คํ๋ ํด๋ ์ ํ์
// ID Token์ JWT(JSON Web Token) ํ์์ผ๋ก ๋ฐ๊ธ๋๋ฉฐ,
// ์ฌ์ฉ์ ์ธ์ฆ ํ OpenID Provider๊ฐ Relying Party(ํด๋ผ์ด์ธํธ)์๊ฒ ์ ๋ฌํ๋ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๋ด๊ณ ์๋ค.
{
// ํ์ ํด๋ ์
"iss": "https://op.example.com", // ๋ฐ๊ธ์
"sub": "user123", // ์ฌ์ฉ์ ์๋ณ์
"aud": "client123", // ํด๋ผ์ด์ธํธ ID
"exp": 1516239022, // ๋ง๋ฃ ์๊ฐ
"iat": 1516235422, // ๋ฐ๊ธ ์๊ฐ
// ์ ํ์ ํด๋ ์
"auth_time": 1516239022, // ์ธ์ฆ ์๊ฐ
"nonce": "abc123", // ์ฌ์ ๊ณต๊ฒฉ ๋ฐฉ์ง
"acr": "urn:mace:incommon:iap:silver", // ์ธ์ฆ ๋ฌธ๋งฅ ํด๋์ค
"amr": ["pwd"], // ์ธ์ฆ ๋ฐฉ๋ฒ
"azp": "client123" // ์ธ๊ฐ๋ ๋น์ฌ์
}
- Access Token
- ์ฌ์ฉ๋ชฉ์
- ์ธ๊ฐ(Authorization) ๋ชฉ์
- ๋ฆฌ์์ค ์ ๊ทผ ๊ถํ ํํ
- ๋ฆฌ์์ค ์๋ฒ๋ง๋ค ๋ค๋ฅธ ์๊ตฌ์ฌํญ ๊ฐ๋ฅ
- ์ฌ์ฉ๋ฒ์
- ๋ค์ํ ๋ฆฌ์์ค ์๋ฒ์์ ์ฌ์ฉ
- ๊ฐ ์๋น์ค์ ๋ง๋ ์ ์ฐํ ๊ตฌ์กฐ ํ์
// Access Token - ํฌ๋งท ์์ ๋ก์
// ์์ 1: ๋ถํฌ๋ช
ํ ํฐ
"abcd1234xyz789..."
// ์์ 2: JWT ํ์
{
"sub": "user123",
"scope": ["read", "write"],
"exp": 1516239022
}
// ์์ 3: ์ปค์คํ
ํฌ๋งท
{
"userId": "user123",
"permissions": ["admin", "user"],
"customClaim": "value"
}
- ID Token VS Access Token
ID Token๊ณผ Access Token์ด ์ด๋ป๊ฒ ๋ค๋ฅธ์ง, ๋ฌด์์ ์ํด ์ฌ์ฉํ๋์ง ์ดํดํ๊ณ ๋์ด๊ฐ์.
์์ OpenID Connect์ Authorization Code Flow ๋ค์ด์ด๊ทธ๋จ์์
7๋ฒ. ID Token์ ํตํด์ ์ธ์ฆ์ ๋ฐ๊ณ , 8๋ฒ. AccessToken์ ๊ฐ์ง๊ณ userinfo๋ฅผ ์์ฒญํ๊ฑฐ๋ผ๊ณ ์ดํดํ ์ ์๋ค.
ID Token | Access Token | |
๋ชฉ์ | ์ธ์ฆ(Authentication) | ์ธ๊ฐ(Authorization) |
์ง๋ฌธ | "์ด ์ฌ์ฉ์๊ฐ ๋๊ตฌ์ธ๊ฐ?" | "์ด ์์ฒญ์ด ํ์ฉ๋๋๊ฐ?" |
์ฌ์ฉ | ํด๋ผ์ด์ธํธ ์ฑ์์ ์ง์ ์ฌ์ฉ | API ์์ฒญ ์ ์ฌ์ฉ |
ํ์ | ํญ์ JWT | ์์ ๋ก์ |
๊ฒ์ฆ | ํด๋ผ์ด์ธํธ๊ฐ ๋ฐ๋์ ๊ฒ์ฆ | ๋ฆฌ์์ค ์๋ฒ๊ฐ ๊ฒ์ฆ |
- ID Token
// Access Token - ํฌ๋งท ์์ ๋ก์
// ์์ 1: ๋ถํฌ๋ช
ํ ํฐ
"abcd1234xyz789..."
// ์์ 2: JWT ํ์
{
"sub": "user123",
"scope": ["read", "write"],
"exp": 1516239022
}
// ์์ 3: ์ปค์คํ
ํฌ๋งท
{
"userId": "user123",
"permissions": ["admin", "user"],
"customClaim": "value"
}
- Access Token
// Access Token์ "๋ฌด์์ ํ ์ ์๋์ง"๋ฅผ ๋ํ๋
// API ํธ์ถ ์ ์ฌ์ฉ
async function fetchUserData(accessToken) {
// UserInfo ์๋ํฌ์ธํธ ํธ์ถ
const response = await fetch('https://api.example.com/userinfo', {
headers: {
'Authorization': `Bearer ${accessToken}`
}
});
// ๋ค๋ฅธ API ํธ์ถ
const orders = await fetch('https://api.example.com/orders', {
headers: {
'Authorization': `Bearer ${accessToken}`
}
});
}
- ์ฌ์ฉ์์
// ----------------------------------------------------------------
// ์ฌ์ฉ ์๋๋ฆฌ์ค
class AuthService {
async handleAuthenticationFlow(authCode) {
// 1. ํ ํฐ ๋ฐ๊ธฐ
const tokens = await fetchTokens(authCode);
const { id_token, access_token } = tokens;
// 2. ID Token์ผ๋ก ์ฌ์ฉ์ ์ธ์ฆ
if (this.validateIdToken(id_token)) {
// ๋ก๊ทธ์ธ ์ฒ๋ฆฌ
const userData = jwt.decode(id_token);
this.setLoggedInUser({
id: userData.sub,
name: userData.name
});
}
// 3. Access Token์ผ๋ก ์ถ๊ฐ ์ ๋ณด ์์ฒญ
const userDetails = await this.fetchUserDetails(access_token);
const userOrders = await this.fetchUserOrders(access_token);
}
}
// ----------------------------------------------------------------
// ์ค์ ๊ตฌํ ์์
// 1. ๋ก๊ทธ์ธ ํ ๋ ํ ํฐ ์ ์ฅ
function handleLoginSuccess(tokens) {
const { id_token, access_token } = tokens;
// ID Token์ผ๋ก ์ฌ์ฉ์ ์ ๋ณด ์ค์
const userInfo = jwt.decode(id_token);
localStorage.setItem('user', JSON.stringify({
id: userInfo.sub,
name: userInfo.name,
email: userInfo.email
}));
// Access Token ์ ์ฅ
localStorage.setItem('access_token', access_token);
}
// 2. API ํธ์ถ ์ Access Token ์ฌ์ฉ
async function callAPI() {
const accessToken = localStorage.getItem('access_token');
try {
const response = await fetch('https://api.example.com/data', {
headers: {
'Authorization': `Bearer ${accessToken}`
}
});
return await response.json();
} catch (error) {
// ํ ํฐ ๋ง๋ฃ ๋ฑ ์ฒ๋ฆฌ
handleAuthError(error);
}
}
// 3. ์ฌ์ฉ์ ์ ๋ณด ํ์ธ ์ ID Token ์ ๋ณด ์ฌ์ฉ
function getCurrentUser() {
return JSON.parse(localStorage.getItem('user'));
}
- KeyCloak์์ AccessToken์ผ๋ก JWT๋ฅผ ์ฌ์ฉ ๐
์ด์ฐฝ๊ธฐ๋ถํฐ KeyCloak์ Access Token์ผ๋ก JWT๋ฅผ ์ฌ์ฉํด์์ผ๋ฉฐ, JWT๋ฅผ ์ฌ์ฉํจ์ ๋ฐ๋ผ ์๋์ ๊ฐ์ ์ฅ์ ์ ๊ฐ์ง๊ฒ ๋์๋ค.
(์์ ์ค๋ช
๋๋ฐ๋ก AccessToken์ ํฌ๋ฉง์ ๋ํ ๊ท์ ์ด ์๋ค.)
JWT๋ฅผ ์ง์ํ๋ OpenID Connect/OAuth 2.0 ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋๊ฒ์ ๊ถ์ฅํ๋ค. (๋ณด์์ทจ์ฝ์ ์์)
// -------------------------------------------------------------------
// Access Token์ผ๋ก ์ผ๋ฐํ ํฐ (๋ถํฌ๋ช
ํ ํฐ) ์ฌ์ฉ์
+-----------+ +------------------+
| ํฌํ ๋ถ ์ฑ | ----(1) Access Token ์ ๋ฌ ------> | ๊ตฌ๊ธ ๋๋ผ์ด๋ธ |
| | <---(2) ํ ํฐ ๊ฒ์ฆ ์์ฒญ ---------- | (๋ฆฌ์์ค ์๋ฒ) |
| | ----(3) ์ฌ์ฉ์ ์ ๋ณด ์์ฒญ --------> | |
| | <---(4) ์ค์ ๋ฆฌ์์ค ์๋ต --------- | |
+-----------+ +------------------+
|
+------------------+
| ๊ตฌ๊ธ OAuth |
| (์ธ์ฆ ์๋ฒ) |
+------------------+
// ๋ถํฌ๋ช
ํ ํฐ ์ฒ๋ฆฌ ๊ณผ์ ์์
async function handleOpaqueToken() {
// ์ด 3๋ฒ์ ๋คํธ์ํฌ ์์ฒญ
const steps = [
'1. ํ ํฐ ๊ฒ์ฆ ์์ฒญ โก๏ธ Keycloak',
'2. UserInfo ์์ฒญ โก๏ธ Keycloak',
'3. ์ค์ ๋ฆฌ์์ค ์ฒ๋ฆฌ'
];
// ์ฒ๋ฆฌ ์๊ฐ = ๋คํธ์ํฌ ์ง์ฐ x 3
}
// -------------------------------------------------------------------
// Access Token์ผ๋ก JWT ์ฌ์ฉ์
// JWT์ ์ด๋ฏธ ํ์ํ ์ ๋ณด๊ฐ ํฌํจ๋์ด ์์ด์ ์ถ๊ฐ ์์ฒญ ์์ด ๋ฐ๋ก ๋ฆฌ์์ค ์ ๊ทผ ๊ฐ๋ฅ
+-----------+ +------------------+
| ํฌํ ๋ถ ์ฑ | ---(1) JWT Access Token ์ ๋ฌ---> | ๊ตฌ๊ธ ๋๋ผ์ด๋ธ |
| | | |
| | <---(2) ์ค์ ๋ฆฌ์์ค ์๋ต -------- | (๋ฆฌ์์ค ์๋ฒ) |
+-----------+ +------------------+
// JWT ์ฒ๋ฆฌ ๊ณผ์
async function handleJWT() {
// ์ด 1๋ฒ์ ๋คํธ์ํฌ ์์ฒญ
const steps = [
'1. JWT ๋ก์ปฌ ๊ฒ์ฆ (๋คํธ์ํฌ ์์ฒญ ์์)',
'2. ๊ถํ ํ์ธ (๋คํธ์ํฌ ์์ฒญ ์์)',
'3. ์ค์ ๋ฆฌ์์ค ์ฒ๋ฆฌ'
];
// ์ฒ๋ฆฌ ์๊ฐ = ๋คํธ์ํฌ ์ง์ฐ x 1
}
- ์์ฒด ํฌํจ(Self-contained)
- ํ์ํ ๋ชจ๋ ์ ๋ณด๊ฐ ํ ํฐ ์์ ํฌํจ
- ์ถ๊ฐ ์กฐํ ๋ถํ์
- ์ฑ๋ฅ ํฅ์
- ๋คํธ์ํฌ ์์ฒญ ๊ฐ์
- ์๋ฒ ๋ถํ ๊ฐ์
- ํ์ฅ์ฑ
- ํ์ํ ์ ๋ณด๋ฅผ ํด๋ ์์ผ๋ก ์ถ๊ฐ ๊ฐ๋ฅ
- ๋ค์ํ ์ธ์ฆ/์ธ๊ฐ ์๋๋ฆฌ์ค ์ง์
- ํ์ค ํด๋ ์ ์ธํธ
ํด๋ ์(Claim)์ ID Token์์ ์ฌ์ฉ๋๋ฉฐ,
"์ฃผ์ฅ" ๋๋ "๋ช
์ธ"๋ผ๋ ์๋ฏธ๋ก, ์ฌ์ฉ์๋ ์ํฐํฐ์ ๋ํ ์ ๋ณด๋ฅผ ํํํ๋ key-value ์์ ๋งํ๋ค.
// ID Token ํ์ ํด๋ ์
{
"iss": "https://auth.example.com", // ํ ํฐ ๋ฐ๊ธ์
"sub": "248289761001", // ์ฌ์ฉ์ ๊ณ ์ ์๋ณ์
"aud": "client_id", // ํ ํฐ ์์ ์
"exp": 1516239022, // ๋ง๋ฃ ์๊ฐ
"iat": 1516239022 // ๋ฐ๊ธ ์๊ฐ
}
// ํ๋กํ ๊ด๋ จ ํด๋ ์
{
"name": "John Doe",
"family_name": "Doe",
"given_name": "John",
"middle_name": "William",
"nickname": "Johnny",
"preferred_username": "j.doe",
"profile": "https://example.com/profile",
"picture": "https://example.com/photo.jpg",
"website": "http://johndoe.com",
"gender": "male",
"birthdate": "1990-01-01",
"zoneinfo": "Europe/Paris",
"locale": "en-US",
"updated_at": 1516239022
}
// ์ด๋ฉ์ผ ๊ด๋ จ ํด๋ ์
{
"email": "johndoe@example.com",
"email_verified": true // ์ด๋ฉ์ผ ์ธ์ฆ ์ฌ๋ถ
}
// ์ฃผ์ ๊ด๋ จ ํด๋ ์
{
"address": {
"street_address": "123 Main St",
"locality": "Anytown", // ์/๊ตฐ/๊ตฌ
"region": "State", // ๋/์
"postal_code": "12345",
"country": "US"
}
}
- ์๋ํฌ์ธํธ ์ ์ (OICD/ KeyCloak)
// OpenID Configuration ์๋ํฌ์ธํธ
GET /.well-known/openid-configuration
// ์๋ต
{
"issuer": "https://op.example.com",
"authorization_endpoint": "https://op.example.com/auth",
"token_endpoint": "https://op.example.com/token",
"userinfo_endpoint": "https://op.example.com/userinfo",
"jwks_uri": "https://op.example.com/jwks",
"registration_endpoint": "https://op.example.com/register",
"scopes_supported": ["openid", "profile", "email"],
"response_types_supported": ["code", "token", "id_token"],
"grant_types_supported": ["authorization_code", "implicit"],
"subject_types_supported": ["public", "pairwise"],
"id_token_signing_alg_values_supported": ["RS256", "ES256"]
}
// Keycloak์ OpenID Configuration ์๋ํฌ์ธํธ
// ๋ก์ปฌ ๊ฐ๋ฐ ํ๊ฒฝ http://localhost:8080/realms/master/.well-known/openid-configuration
// ์ค์ ํ๊ฒฝ https://auth.example.com/realms/my-realm/.well-known/openid-configuration
http(s)://{keycloak-host}/realms/{realm-name}/.well-known/openid-configuration
// ์๋ต์์
{
"issuer": "http://localhost:8080/realms/master",
"authorization_endpoint": "http://localhost:8080/realms/master/protocol/openid-connect/auth",
"token_endpoint": "http://localhost:8080/realms/master/protocol/openid-connect/token",
"introspection_endpoint": "http://localhost:8080/realms/master/protocol/openid-connect/token/introspect",
"userinfo_endpoint": "http://localhost:8080/realms/master/protocol/openid-connect/userinfo",
"end_session_endpoint": "http://localhost:8080/realms/master/protocol/openid-connect/logout",
"jwks_uri": "http://localhost:8080/realms/master/protocol/openid-connect/certs",
"check_session_iframe": "http://localhost:8080/realms/master/protocol/openid-connect/login-status-iframe.html",
"grant_types_supported": [
"authorization_code",
"implicit",
"refresh_token",
"password",
"client_credentials"
],
"response_types_supported": [
"code",
"none",
"id_token",
"token",
"id_token token",
"code id_token",
"code token",
"code id_token token"
],
"subject_types_supported": ["public"],
"id_token_signing_alg_values_supported": ["RS256"],
"userinfo_signing_alg_values_supported": ["RS256"],
"request_object_signing_alg_values_supported": ["none", "RS256"],
"response_modes_supported": ["query", "fragment", "form_post"],
"registration_endpoint": "http://localhost:8080/realms/master/clients-registrations/openid-connect",
"token_endpoint_auth_methods_supported": [
"private_key_jwt",
"client_secret_basic",
"client_secret_post",
"tls_client_auth",
"client_secret_jwt"
],
"token_endpoint_auth_signing_alg_values_supported": ["RS256"],
"claims_supported": [
"aud",
"sub",
"iss",
"auth_time",
"name",
"given_name",
"family_name",
"preferred_username",
"email",
"acr"
],
"claim_types_supported": ["normal"],
"claims_parameter_supported": false,
"scopes_supported": ["openid", "offline_access", "profile", "email", "address", "phone", "roles", "web-origins"],
"request_parameter_supported": true,
"request_uri_parameter_supported": true,
"require_request_uri_registration": true,
"code_challenge_methods_supported": ["plain", "S256"],
"tls_client_certificate_bound_access_tokens": true,
"revocation_endpoint": "http://localhost:8080/realms/master/protocol/openid-connect/revoke",
"backchannel_logout_supported": true,
"backchannel_logout_session_supported": true
}
- OIDC์ ์ฃผ์ ํ์ฅ ๊ธฐ๋ฅ #1 Discovery ๐
OP์ ์๋ํฌ์ธํธ์ ๊ธฐ๋ฅ์ ์๋์ผ๋ก ๋ฐ๊ฒฌํ๋ ๋ฉ์ปค๋์ฆ
GET /.well-known/openid-configuration
// ์๋ต ์์
{
"issuer": "https://auth.example.com",
"authorization_endpoint": "https://auth.example.com/auth",
"token_endpoint": "https://auth.example.com/token",
"userinfo_endpoint": "https://auth.example.com/userinfo",
"jwks_uri": "https://auth.example.com/jwks",
"registration_endpoint": "https://auth.example.com/register",
// ... ๊ธฐํ ์ค์ ๋ค
}
- OIDC์ ์ฃผ์ ํ์ฅ ๊ธฐ๋ฅ #2 Dynamic Registration ๐
ํด๋ผ์ด์ธํธ๊ฐ ๋์ ์ผ๋ก OP์ ๋ฑ๋กํ๋ ๊ธฐ๋ฅ
// ๋ฑ๋ก ์์ฒญ
POST /register HTTP/1.1
{
"application_type": "web",
"redirect_uris": [
"https://client.example.com/callback"
],
"client_name": "My Web App",
"logo_uri": "https://client.example.com/logo.png",
"subject_type": "pairwise"
}
// ์๋ต
{
"client_id": "abc123",
"client_secret": "xyz789",
"registration_access_token": "reg-token-123"
}
- OIDC์ ์ฃผ์ ํ์ฅ ๊ธฐ๋ฅ #3 Session Management ๐
๋ธ๋ผ์ฐ์ ๊ธฐ๋ฐ ์ธ์
๊ด๋ฆฌ
OpenID ์ ๊ณต์์ ์ต์ข
์ฌ์ฉ์์ ์ธ์ฆ ์ธ์
์ ๋ชจ๋ํฐ๋ง ํ๋ ๋ฐฉ๋ฒ๊ณผ ํด๋ผ์ด์ธํธ๊ฐ ๋ก๊ทธ๊ฐ์ ๋ฐฉ๋ฒ์ ์ ์ํจ
// ํด๋ผ์ด์ธํธ ์ธก ๊ตฌํ
class SessionManager {
checkSession() {
const iframe = document.createElement('iframe');
iframe.src = 'https://op.example.com/check-session';
iframe.onload = () => {
// ์ธ์
์ํ ํ์ธ
iframe.contentWindow.postMessage(
{ clientId: 'abc123' },
'https://op.example.com'
);
};
}
handleSessionChange(event) {
if (event.data.sessionState === 'changed') {
// ๋ก๊ทธ์์ ์ฒ๋ฆฌ
this.logout();
}
}
}
- OIDC์ ์ฃผ์ ํ์ฅ ๊ธฐ๋ฅ #4 Front-channel Logout ๐ช
๋ธ๋ผ์ฐ์ ๋ฅผ ํตํ ๋ก๊ทธ์์ (iframe)
<!-- ๋ก๊ทธ์์ iframe -->
<iframe
src="https://op.example.com/logout?
client_id=abc123&
logout_uri=https://client.example.com/logout"
style="display:none">
</iframe>
<script>
function handleLogout() {
// ์ฌ๋ฌ RP์ ๋ก๊ทธ์์ ์๋ฆผ
logoutIframes.forEach(iframe => {
iframe.src = iframe.getAttribute('logout-uri');
});
}
</script>
- OIDC์ ์ฃผ์ ํ์ฅ ๊ธฐ๋ฅ #5 Back-channel Logout ๐
์๋ฒ ๊ฐ ์ง์ ํต์ ์ ํตํ ๋ก๊ทธ์์
// RP์ ๋ฐฑ์ฑ๋ ๋ก๊ทธ์์ ์๋ํฌ์ธํธ
app.post('/backchannel_logout', async (req, res) => {
const logoutToken = req.body.logout_token;
try {
// ๋ก๊ทธ์์ ํ ํฐ ๊ฒ์ฆ
const verified = await verifyLogoutToken(logoutToken);
if (verified) {
// ํด๋น ์ฌ์ฉ์ ์ธ์
์ข
๋ฃ
await terminateUserSession(verified.sub);
res.status(200).send('OK');
}
} catch (error) {
res.status(400).send('Invalid logout token');
}
});
๐ SAML
SAML์ ์น ๊ธฐ๋ฐ์ Single Sign-On(SSO) ๋ฐ ์ธ์ฆ์ ์ํ XML ๊ธฐ๋ฐ์ ํ์ค์ด๋ค.
์ฃผ๋ก ๊ธฐ์
ํ๊ฒฝ์์ ์ฌ์ฉ๋๋ฉฐ, ์ฌ์ฉ์ ์ธ์ฆ ์ ๋ณด๋ฅผ ์์ ํ๊ฒ ๊ตํํ๋ ๋ฐ ์ฌ์ฉํ๋ค.
- ์ฃผ์ ๊ฐ๋
์ต์ข
์ฌ์ฉ์ (End User) |
์ค์ ์ธ์ฆ์ ์ํํ๋ ์ฌ์ฉ์ |
์๋น์ค ์ ๊ณต์ (Service Provider, SP) |
์ฌ์ฉ์๊ฐ ์ ๊ทผํ๋ ค๋ ์ ํ๋ฆฌ์ผ์ด์ ๋๋ ์๋น์ค. SAML ์ธ์ฆ ์์ฒญ์ ์ฒ๋ฆฌ (์, ๋ก๊ทธ์ธํ์ด์ง) |
์์ด๋ดํฐํฐ ์ ๊ณต์ (Identity Provider, IdP) |
์ฌ์ฉ์์ ์ ์์ ์ฆ๋ช ํ๊ณ SAML ์ด์ค์ ์ ๋ฐ๊ธํ๋ ์๋ฒ (์: ADFS, Okta) |
SAML ์ด์ค์
(SAML Assertion) |
์ฌ์ฉ์์ ์ธ์ฆ ์ ๋ณด๋ฅผ ํฌํจํ๋ XML ํ์์ ๋ฐ์ดํฐ |
SAML ํ๋กํ ์ฝ | SAML ์ด์ค์ ์ ์ ์กํ๋ ๋ฐ ์ฌ์ฉ๋๋ ํ๋กํ ์ฝ. |
- SAML ์ธ์ฆ ํ๋ฆ
- ์ต์ข ์ฌ์ฉ์์ ์๋น์ค ์ ๊ทผ: ์ฌ์ฉ์๊ฐ ๋ธ๋ผ์ฐ์ ๋ฅผ ํตํด ์๋น์ค ์ ๊ณต์(SP)์ ๋ฆฌ์์ค์ ์ ๊ทผ์ ์๋ํจ
- SP ์ธ์ฆ ํ๋ฆ ์์
- SP๋ ์ฌ์ฉ์๊ฐ ์ธ์ฆ๋์ง ์์์์ ํ์ธํจ
- SP-initiated flow(SP ์์ ํ๋ฆ)๊ฐ ์์๋จ - SAML ์ธ์ฆ ์์ฒญ ๋ฆฌ๋ค์ด๋ ํธ: ๐ <samlp:AuthnRequest> ์ฌ์ฉ
SP๋ ๋ธ๋ผ์ฐ์ ๋ฅผ ํตํด SAML ์ธ์ฆ ์์ฒญ์ IdP๋ก ๋ฆฌ๋ค์ด๋ ํธํ๋ค. - IdP ์ธ์ฆ ํ๋ฆ ์์: IdP-initiated flow(IdP ์์ ํ๋ฆ)๊ฐ ์์๋๋ค.
- ์ฌ์ฉ์ ์ธ์ฆ ๋ฐ SAML ์ด์ค์
์์ฑ: ๐ <saml:Assertion> ์์ฑ
- IdP๋ ์ฌ์ฉ์๋ฅผ ์ธ์ฆํ๊ณ
- ์ธ์ฆ ์ฑ๊ณต ์ SAML ์ด์ค์ ์ ์์ฑํ์ฌ ๋ธ๋ผ์ฐ์ ๋ก ์ ์กํ๋ค. - SAML ์ด์ค์
์ ๋ฌ: ๐ <saml:Assertion> ์ ๋ฌ
๋ธ๋ผ์ฐ์ ๋ IdP๋ก๋ถํฐ ๋ฐ์ SAML ์ด์ค์ ์ SP๋ก ์ ๋ฌํ๋ค. - ๋ณด์ ์ปจํ ์คํธ ์ค์ : SP๋ ์ฌ์ฉ์ ์ธ์ฆ์ด ์๋ฃ๋๋ฉด ๋ณด์ ์ปจํ ์คํธ๋ฅผ ๋ธ๋ผ์ฐ์ ๋ก ์ ์กํ๋ค.
- ๋ฆฌ์์ค ์์ฒญ: ์ธ์ฆ๋ ์ฌ์ฉ์๊ฐ SP์ ๋ฆฌ์์ค๋ฅผ ์์ฒญํ๋ค.
- ๋ฆฌ์์ค ์๋ต: SP๋ ์์ฒญ๋ ๋ฆฌ์์ค๋ก ์๋ตํ๋ค.
โญ๏ธโญ๏ธโญ๏ธโญ๏ธโญ๏ธ
[Service Provider] ↔ [User's Browser] ↔ [Identity Provider]
ํนํ SAML ์ธ์ฆ ํ๋ฆ์์ ์ค์ํ ์ ์ ๋ชจ๋ ํต์ ์ด ์ฌ์ฉ์์ ๋ธ๋ผ์ฐ์ ๋ฅผ ํตํด ์ด๋ฃจ์ด์ง๋ฉฐ(Browser Agent),
IdP์ SP๋ ์ง์ ํต์ ํ์ง ์๋๋ค๋ ๊ฒ์ด๋ค. (SAML Browser POST Profile ๋๋ Web Browser SSO Profile ๋ผ๊ณ ๋ ํจ)
์ด๋ฌํ ๋ฐฉ์์ ์น ๊ธฐ๋ฐ SSO(Single Sign-On)์ ํนํ ์ ํฉํ๋ค.
- SAML ์ธ์ฆ์์ฒญ ๋ฐ ์ด์ค์ ์ํ
<!-- SAML ์ธ์ฆ ์์ฒญ -->
# ์๋น์ค ์ ๊ณต์(SP)๊ฐ ์์ด๋ดํฐํฐ ์ ๊ณต์(IdP)์๊ฒ ์ฌ์ฉ์์ ์ธ์ฆ์ ์์ฒญํ๊ธฐ ์ํด ์์ฑํ๋ ๋ฉ์์ง
<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
ID="_123456789"
Version="2.0"
IssueInstant="2023-10-01T00:00:00Z"
Destination="https://idp.com/SAML2/SSO">
<saml:Issuer>https://yourapp.com</saml:Issuer>
<samlp:NameIDPolicy AllowCreate="true" />
</samlp:AuthnRequest>
<!-- SAML ์ด์ ์
-->
# IdP๊ฐ ์ฌ์ฉ์๋ฅผ ์ธ์ฆํ ํ, SP์๊ฒ ์ ๋ฌํ๋ ๋ฉ์์ง๋ก, ์ฌ์ฉ์์ ์ธ์ฆ ์ ๋ณด๋ฅผ ํฌํจ
<saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="_123456789"
IssueInstant="2023-10-01T00:00:00Z"
Version="2.0">
<saml:Issuer>https://idp.com</saml:Issuer>
<saml:Subject>
<saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">user@example.com</saml:NameID>
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<saml:SubjectConfirmationData NotOnOrAfter="2023-10-01T01:00:00Z"
Recipient="https://yourapp.com/SAML/SSO"/>
</saml:SubjectConfirmation>
</saml:Subject>
<saml:Conditions NotBefore="2023-10-01T00:00:00Z"
NotOnOrAfter="2023-10-01T01:00:00Z">
<saml:AudienceRestriction>
<saml:Audience>https://yourapp.com</saml:Audience>
</saml:AudienceRestriction>
</saml:Conditions>
<saml:AuthnStatement AuthnInstant="2023-10-01T00:00:00Z">
<saml:AuthnContext>
<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef>
</saml:AuthnContext>
</saml:AuthnStatement>
</saml:Assertion>
๐ Zero Trust
"์ ๋ ์ ๋ขฐํ์ง ๋ง๊ณ ํญ์ ๊ฒ์ฆํ๋ผ(Never Trust, Always Verify)"๋ ๊ฐ๋ ์ด๋ค.
๋คํธ์ํฌ ๋ด๋ถ์ ์ธ๋ถ ๋ชจ๋ ์ฌ์ฉ์์ ์ฅ์น์ ๋ํด ํญ์ ์ ๋ขฐ๋ฅผ ์ฌ๊ฒ์ฆํ๊ณ ์ธ์ฆํ๋ฉฐ(์ ๊ทผ๊ฒ์ฆ),
์ฌ์ฉ์์ ์ฅ์น๊ฐ ํ์ํ ์ต์ํ์ ๊ถํ๋ง ๋ถ์ฌํ๊ณ (์ต์๊ถํ์ ๊ทผ์ ์ด),
๋ชจ๋ ์ก์ธ์ค์ ์์
์ ๋ชจ๋ํฐ๋งํ๊ณ ๋ก๊น
ํ์ฌ ์ด์์งํ๋ฅผ ๋ถ์ํ๊ณ ๋์ํ๋ค.
- ๊ธฐ์กด ๋ณด์๊ณผ ๋ญ๊ฐ ๋ค๋ฅธ๊ฐ?
๊ธฐ์กด ๋ณด์๋ชจ๋ธ์์๋ ๋ด๋ถ ๋คํธ์ํฌ๋ ์ ๋ขฐํ๋ค๋ ๊ฐ์ ์ผ๋ก ์ค๊ณ๋์์ผ๋ฉฐ, ์ธ๋ถ์์๋ถํฐ ์ ๊ทผํ๋ ์ฌ์ฉ์๋ฐ ์ฅ์น์ ๋ํ ๋ณด์์ ์ง์ค๋์ด์๋ค.
Zero Trust๋ ๋คํธ์ํฌ ๋ด๋ถ์ ์ธ๋ถ์ ๊ตฌ๋ถ์ ์์ ๊ณ , ๋ชจ๋ ์ ๊ทผ์ ์ง์์ ์ผ๋ก ํ๊ฐํ๊ณ ๊ฒ์ฆํ๋ ๊ฐ๋
์ด๋ค.
- ๊ธฐ์กด ๋ณด์๋ชจ๋ธ vs Zelo Trust
๊ธฐ์ค | ๊ธฐ์กด ๋ณด์ ๋ชจ๋ธ | Zero Trust(Zelo Trust) |
๋ณด์ ๊ฒฝ๊ณ | ๋ด๋ถ์ ์ธ๋ถ๋ก ๋ช ํํ๊ฒ ๊ตฌ๋ถ | ๊ฒฝ๊ณ๊ฐ ์์ผ๋ฉฐ ๋ชจ๋ ์ ๊ทผ์ ๊ฒ์ฆ |
๋ด๋ถ ์ ๋ขฐ ์์ค | ๋ด๋ถ ๋คํธ์ํฌ๋ ์ ๋ขฐํ ์ ์์ | ๋ด๋ถ ๋คํธ์ํฌ๋ ์ ๋ขฐํ์ง ์์ |
์ ๊ทผ ์ ์ด | ๋คํธ์ํฌ ๊ธฐ๋ฐ(๋ฐฉํ๋ฒฝ, VPN) | ์ฌ์ฉ์, ์ฅ์น, ์ปจํ ์คํธ ๊ธฐ๋ฐ ์ ๊ทผ ์ ์ด, ์ ์ฑ ๊ธฐ๋ฐ |
์ธ์ฆ ์ ์ฐจ | ์ด๊ธฐ ์ธ์ฆ๋ง ์๊ตฌ | ์ง์์ ์ธ ์ธ์ฆ ๋ฐ ์ ์ฑ ํ๊ฐ |
์ํ ํ์ง | ์ฃผ๋ก ์ธ๋ถ ์ํ ํ์ง์ ์ง์ค | ๋ด๋ถ ๋ฐ ์ธ๋ถ ์ํ์ ๋ชจ๋ ์ง์์ ์ผ๋ก ๋ชจ๋ํฐ๋ง |
๊ตฌํ์์ | Firewall, VPN, IDS/IPS | Keycloak๊ณผ AWS SSO์ ๊ฐ์ IAM ์๋ฃจ์ |
์ฆ, ๊ธฐ์กด ๋ณด์ ๋ชจ๋ธ์ ๋ด๋ถ ๋คํธ์ํฌ์ ์ ๋ขฐ๋ฅผ ์ ์ ๋ก ํ๊ณ , ์ธ๋ถ์์์ ์ ๊ทผ์ ์ฐจ๋จํ๋ ๋ฐ ์ค์ ์ ๋๋ค.
Zero Trust(Zelo Trust)๋ ๋ด๋ถ์ ์ธ๋ถ๋ฅผ ๊ตฌ๋ถํ์ง ์๊ณ , ๋ชจ๋ ์ ๊ทผ ์์ฒญ์ ์ง์์ ์ผ๋ก ๊ฒ์ฆํ๊ณ ์ต์ ๊ถํ ์ ๊ทผ์ ์์น์ผ๋ก ํ๋ค.