refactor: 문서 개선 중
This commit is contained in:
@@ -0,0 +1,385 @@
|
||||
{
|
||||
"schemaVersion": 4,
|
||||
"runId": "2026-09-19-1928-remediation-07-concept-idp-brokering",
|
||||
"project": "keycloak",
|
||||
"record": "docs/keycloak/tech-log-studio/oauth-oidc-auth-boundary/concept/concept-idp-brokering.md",
|
||||
"startedAt": "2026-09-19T10:28:06+00:00",
|
||||
"finishedAt": "2026-09-19T10:28:09+00:00",
|
||||
"stages": [
|
||||
{
|
||||
"id": "S1",
|
||||
"name": "코드베이스 → SSOT",
|
||||
"skill": "analyzing-codebase-for-tech-log",
|
||||
"runBy": "ssot-analyst",
|
||||
"status": "SKIPPED",
|
||||
"skipReason": "기존 SSOT docs/keycloak/final/document.md가 있고 이번 리뷰는 이미 반영된 2026-09-19 Record 결과의 current remediation 원장 생성이다. SSOT 본문을 다시 수정하지 않는다.",
|
||||
"skillEcho": "",
|
||||
"skillRevision": "edd45dfec66d155a621cd41186b83b5582f2244c",
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"gates": [],
|
||||
"notes": "",
|
||||
"startedAt": null,
|
||||
"finishedAt": "2026-09-19T10:28:06+00:00",
|
||||
"elapsedSeconds": 0,
|
||||
"finishedBy": "chatgpt-current-remediation"
|
||||
},
|
||||
{
|
||||
"id": "S2",
|
||||
"name": "SSOT → 분해 계약",
|
||||
"skill": "deriving-tech-log-root-tree",
|
||||
"runBy": "tree-deriver",
|
||||
"status": "SKIPPED",
|
||||
"skipReason": "해당 기록은 tech-log-tree.json의 기존 PROMOTE/CONFIRMED 노드이며 Tree/분해 계약이 이미 PASS다. 이번 remediation에서는 분해 계약을 변경하지 않는다.",
|
||||
"skillEcho": "",
|
||||
"skillRevision": "ab59130196d79e947b32e3b5e6b75335a9e5c1eb",
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"gates": [],
|
||||
"notes": "",
|
||||
"startedAt": null,
|
||||
"finishedAt": "2026-09-19T10:28:06+00:00",
|
||||
"elapsedSeconds": 0,
|
||||
"finishedBy": "chatgpt-current-remediation"
|
||||
},
|
||||
{
|
||||
"id": "S3",
|
||||
"name": "글감 → 기록",
|
||||
"skill": "writing-tech-log-records",
|
||||
"runBy": "record-writer",
|
||||
"status": "DONE",
|
||||
"skipReason": "",
|
||||
"skillEcho": "**인용한 줄은 SSOT 에서 찾아 대조한다.**",
|
||||
"skillRevision": null,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
"docs/keycloak/tech-log-studio/oauth-oidc-auth-boundary/concept/concept-idp-brokering.md"
|
||||
],
|
||||
"gates": [
|
||||
{
|
||||
"cmd": "python3 scripts/studio-body.py docs/keycloak/tech-log-studio/oauth-oidc-auth-boundary/concept/concept-idp-brokering.md -o runs/keycloak/2026-09-19-1928-remediation-07-concept-idp-brokering/stage/S3/studio-body.md",
|
||||
"exit": 0,
|
||||
"session": "chatgpt-current-remediation",
|
||||
"generation": 1,
|
||||
"at": "2026-09-19T10:28:06+00:00"
|
||||
},
|
||||
{
|
||||
"cmd": "node --experimental-transform-types .agents/skills/writing-tech-log-records/scripts/check_body.mjs runs/keycloak/2026-09-19-1928-remediation-07-concept-idp-brokering/stage/S3/studio-body.md --frontend /shared/codebase/tech-log-frontend",
|
||||
"exit": 0,
|
||||
"session": "chatgpt-current-remediation",
|
||||
"generation": 1,
|
||||
"at": "2026-09-19T10:28:07+00:00"
|
||||
},
|
||||
{
|
||||
"cmd": "node .agents/skills/rewriting-technical-prose-naturally/scripts/check_prose.mjs --warn docs/keycloak/tech-log-studio/oauth-oidc-auth-boundary/concept/concept-idp-brokering.md",
|
||||
"exit": 0,
|
||||
"session": "chatgpt-current-remediation",
|
||||
"generation": 1,
|
||||
"at": "2026-09-19T10:28:07+00:00"
|
||||
},
|
||||
{
|
||||
"cmd": "node .agents/skills/writing-tech-log-records/scripts/check_evidence.mjs keycloak",
|
||||
"exit": 0,
|
||||
"session": "chatgpt-current-remediation",
|
||||
"generation": 1,
|
||||
"at": "2026-09-19T10:28:07+00:00"
|
||||
}
|
||||
],
|
||||
"notes": "현재 remediation에서 record-writer 역할 계약으로 최종 Record를 다시 읽고 S3 gate를 실행했다. 본문은 추가 수정하지 않았다. live source repo는 현재 머신에 없어 repo reconciliation은 fact review에서 UNVERIFIABLE로 기록한다.",
|
||||
"startedAt": null,
|
||||
"finishedAt": "2026-09-19T10:28:07+00:00",
|
||||
"elapsedSeconds": 1,
|
||||
"generation": 1,
|
||||
"owner": "chatgpt-current-remediation",
|
||||
"finishedBy": "chatgpt-current-remediation"
|
||||
},
|
||||
{
|
||||
"id": "S4",
|
||||
"name": "기록 → 그림",
|
||||
"skill": "technical-visualizer",
|
||||
"runBy": "diagram-maker",
|
||||
"status": "DONE",
|
||||
"skipReason": "",
|
||||
"skillEcho": "**그림의 사실은 SSOT 절이 댄다.** 기록은 SSOT 의 인용이라 줄 번호가 근거가 되지 못한다.",
|
||||
"skillRevision": "edd45dfec66d155a621cd41186b83b5582f2244c",
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
"docs/keycloak/final/.techviz/idp-broker-upstream-downstream-boundary/spec.json",
|
||||
"docs/keycloak/final/assets/idp-broker-upstream-downstream-boundary/idp-broker-upstream-downstream-boundary.svg"
|
||||
],
|
||||
"gates": [
|
||||
{
|
||||
"cmd": "TECHVIZ_HOME=/tmp/technical-visualization-haness scripts/techviz lint docs/keycloak/final/.techviz/idp-broker-upstream-downstream-boundary/spec.json --context docs/keycloak/final/.techviz/idp-broker-upstream-downstream-boundary/context.json --json",
|
||||
"exit": 0,
|
||||
"session": "chatgpt-current-remediation",
|
||||
"generation": 1,
|
||||
"at": "2026-09-19T10:28:07+00:00"
|
||||
},
|
||||
{
|
||||
"cmd": "python3 scripts/check-figure-text.py keycloak",
|
||||
"exit": 0,
|
||||
"session": "chatgpt-current-remediation",
|
||||
"generation": 1,
|
||||
"at": "2026-09-19T10:28:07+00:00"
|
||||
},
|
||||
{
|
||||
"cmd": "python3 scripts/check-figure-overlap.py keycloak",
|
||||
"exit": 0,
|
||||
"session": "chatgpt-current-remediation",
|
||||
"generation": 1,
|
||||
"at": "2026-09-19T10:28:07+00:00"
|
||||
},
|
||||
{
|
||||
"cmd": "python3 scripts/preview-figure.py --file docs/keycloak/final/assets/idp-broker-upstream-downstream-boundary/idp-broker-upstream-downstream-boundary.svg -o runs/keycloak/2026-09-19-1928-remediation-07-concept-idp-brokering/stage/S4/preview",
|
||||
"exit": 0,
|
||||
"session": "chatgpt-current-remediation",
|
||||
"generation": 1,
|
||||
"at": "2026-09-19T10:28:08+00:00"
|
||||
},
|
||||
{
|
||||
"cmd": "python3 scripts/check-figure-provenance.py keycloak",
|
||||
"exit": 0,
|
||||
"session": "chatgpt-current-remediation",
|
||||
"generation": 1,
|
||||
"at": "2026-09-19T10:28:08+00:00"
|
||||
}
|
||||
],
|
||||
"notes": "직전 수정에서 생성한 idp-broker-upstream-downstream-boundary 정본과 SVG를 current remediation에서 다시 검증했다. 새로 그리지 않았고 lint/text/overlap/provenance와 PNG preview를 다시 확인했다.",
|
||||
"startedAt": null,
|
||||
"finishedAt": "2026-09-19T10:28:08+00:00",
|
||||
"elapsedSeconds": 1,
|
||||
"generation": 1,
|
||||
"owner": "chatgpt-current-remediation",
|
||||
"finishedBy": "chatgpt-current-remediation"
|
||||
},
|
||||
{
|
||||
"id": "S5",
|
||||
"name": "AI 티 제거",
|
||||
"skill": "rewriting-technical-prose-naturally",
|
||||
"runBy": "prose-rewriter",
|
||||
"status": "DONE",
|
||||
"skipReason": "",
|
||||
"skillEcho": "This is an **editorial** pass. The source's facts, evidence, causal chain, uncertainty, decision status, and technical depth are the contract.",
|
||||
"skillRevision": null,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
"docs/keycloak/tech-log-studio/oauth-oidc-auth-boundary/concept/concept-idp-brokering.md"
|
||||
],
|
||||
"gates": [
|
||||
{
|
||||
"cmd": "node .agents/skills/rewriting-technical-prose-naturally/scripts/check_prose.mjs --warn docs/keycloak/tech-log-studio/oauth-oidc-auth-boundary/concept/concept-idp-brokering.md",
|
||||
"exit": 0,
|
||||
"session": "chatgpt-current-remediation",
|
||||
"generation": 1,
|
||||
"at": "2026-09-19T10:28:08+00:00"
|
||||
},
|
||||
{
|
||||
"cmd": "node .agents/skills/rewriting-technical-prose-naturally/scripts/style_profile.mjs docs/keycloak/tech-log-studio/oauth-oidc-auth-boundary/concept/concept-idp-brokering.md",
|
||||
"exit": 0,
|
||||
"session": "chatgpt-current-remediation",
|
||||
"generation": 1,
|
||||
"at": "2026-09-19T10:28:08+00:00"
|
||||
},
|
||||
{
|
||||
"cmd": "node --experimental-transform-types .agents/skills/writing-tech-log-records/scripts/check_body.mjs runs/keycloak/2026-09-19-1928-remediation-07-concept-idp-brokering/stage/S5/studio-body.md --frontend /shared/codebase/tech-log-frontend",
|
||||
"exit": 0,
|
||||
"session": "chatgpt-current-remediation",
|
||||
"generation": 1,
|
||||
"at": "2026-09-19T10:28:08+00:00"
|
||||
},
|
||||
{
|
||||
"cmd": "node .agents/skills/writing-tech-log-records/scripts/check_evidence.mjs keycloak",
|
||||
"exit": 0,
|
||||
"session": "chatgpt-current-remediation",
|
||||
"generation": 1,
|
||||
"at": "2026-09-19T10:28:08+00:00"
|
||||
}
|
||||
],
|
||||
"notes": "현재 최종 prose를 prose-rewriter 계약으로 다시 읽고 검사했다. check_prose는 PASS했고 style profile은 측정으로만 기록했다. advisory 수치를 맞추기 위한 문장 수정은 하지 않았다.",
|
||||
"startedAt": null,
|
||||
"finishedAt": "2026-09-19T10:28:08+00:00",
|
||||
"elapsedSeconds": 0,
|
||||
"generation": 1,
|
||||
"owner": "chatgpt-current-remediation",
|
||||
"finishedBy": "chatgpt-current-remediation"
|
||||
},
|
||||
{
|
||||
"id": "S6",
|
||||
"name": "일한 사람의 목소리",
|
||||
"skill": "writing-as-the-person-who-did-it",
|
||||
"runBy": "voice-writer",
|
||||
"status": "DONE",
|
||||
"skipReason": "",
|
||||
"skillEcho": "찾은 것이 없으면 **이 스킬은 여기서 끝난다.** 없는 목소리를 채우지 않는다.",
|
||||
"skillRevision": "edd45dfec66d155a621cd41186b83b5582f2244c",
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
"docs/keycloak/tech-log-studio/oauth-oidc-auth-boundary/concept/concept-idp-brokering.md"
|
||||
],
|
||||
"gates": [
|
||||
{
|
||||
"cmd": "node .agents/skills/writing-as-the-person-who-did-it/scripts/check_voice.mjs docs/keycloak/tech-log-studio/oauth-oidc-auth-boundary/concept/concept-idp-brokering.md",
|
||||
"exit": 0,
|
||||
"session": "chatgpt-current-remediation",
|
||||
"generation": 1,
|
||||
"at": "2026-09-19T10:28:08+00:00"
|
||||
},
|
||||
{
|
||||
"cmd": "node .agents/skills/rewriting-technical-prose-naturally/scripts/check_prose.mjs --warn docs/keycloak/tech-log-studio/oauth-oidc-auth-boundary/concept/concept-idp-brokering.md",
|
||||
"exit": 0,
|
||||
"session": "chatgpt-current-remediation",
|
||||
"generation": 1,
|
||||
"at": "2026-09-19T10:28:09+00:00"
|
||||
},
|
||||
{
|
||||
"cmd": "node --experimental-transform-types .agents/skills/writing-tech-log-records/scripts/check_body.mjs runs/keycloak/2026-09-19-1928-remediation-07-concept-idp-brokering/stage/S6/studio-body.md --frontend /shared/codebase/tech-log-frontend",
|
||||
"exit": 0,
|
||||
"session": "chatgpt-current-remediation",
|
||||
"generation": 1,
|
||||
"at": "2026-09-19T10:28:09+00:00"
|
||||
},
|
||||
{
|
||||
"cmd": "node .agents/skills/writing-tech-log-records/scripts/check_evidence.mjs keycloak",
|
||||
"exit": 0,
|
||||
"session": "chatgpt-current-remediation",
|
||||
"generation": 1,
|
||||
"at": "2026-09-19T10:28:09+00:00"
|
||||
}
|
||||
],
|
||||
"notes": "voice-writer 계약으로 현재 최종본을 다시 확인했다. 자료에 없는 경험 문장을 추가하지 않았고 Voice와 재실행 prose/body/evidence gate가 통과했다.",
|
||||
"startedAt": null,
|
||||
"finishedAt": "2026-09-19T10:28:09+00:00",
|
||||
"elapsedSeconds": 1,
|
||||
"generation": 1,
|
||||
"owner": "chatgpt-current-remediation",
|
||||
"finishedBy": "chatgpt-current-remediation"
|
||||
},
|
||||
{
|
||||
"id": "S7",
|
||||
"name": "Studio 저장",
|
||||
"skill": "publishing-tech-log-to-studio",
|
||||
"runBy": "studio-validator",
|
||||
"status": "SKIPPED",
|
||||
"skipReason": "사용자가 이번 리뷰에서 Studio import/save를 요청하지 않았다. 기존 Studio 문서 version을 변경하지 않고 현재 저장소의 remediation 원장과 검증 결과만 남긴다.",
|
||||
"skillEcho": "",
|
||||
"skillRevision": "862e502af3e956b49ccd2ae0a8de3fd32f90df9c",
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"gates": [],
|
||||
"notes": "",
|
||||
"startedAt": null,
|
||||
"finishedAt": "2026-09-19T10:28:09+00:00",
|
||||
"elapsedSeconds": 0,
|
||||
"finishedBy": "chatgpt-current-remediation"
|
||||
}
|
||||
],
|
||||
"qualityReviews": {
|
||||
"commandPedagogy": {
|
||||
"initialAnalysis": {
|
||||
"cmd": "python3 scripts/check-command-pedagogy.py --mode reference docs/keycloak/tech-log-studio/oauth-oidc-auth-boundary/concept/concept-idp-brokering.md -o runs/keycloak/2026-09-19-1928-remediation-07-concept-idp-brokering/stage/S3/command-initial.json",
|
||||
"exit": 0,
|
||||
"shellBlocks": 0,
|
||||
"findings": 0,
|
||||
"majorFindings": 0,
|
||||
"artifact": {
|
||||
"path": "runs/keycloak/2026-09-19-1928-remediation-07-concept-idp-brokering/stage/S3/command-initial.json",
|
||||
"sha256": "e71be99c0ed55465fad9479abb5730290dd04906665c41177150249aef60884e"
|
||||
}
|
||||
},
|
||||
"finalAnalysis": {
|
||||
"cmd": "python3 scripts/check-command-pedagogy.py --mode reference docs/keycloak/tech-log-studio/oauth-oidc-auth-boundary/concept/concept-idp-brokering.md -o runs/keycloak/2026-09-19-1928-remediation-07-concept-idp-brokering/stage/S6/command-final.json",
|
||||
"exit": 0,
|
||||
"shellBlocks": 0,
|
||||
"findings": 0,
|
||||
"majorFindings": 0,
|
||||
"artifact": {
|
||||
"path": "runs/keycloak/2026-09-19-1928-remediation-07-concept-idp-brokering/stage/S6/command-final.json",
|
||||
"sha256": "e71be99c0ed55465fad9479abb5730290dd04906665c41177150249aef60884e"
|
||||
}
|
||||
},
|
||||
"planner": {
|
||||
"runBy": "command-pedagogy-planner",
|
||||
"skill": "writing-practitioner-guides",
|
||||
"status": "SKIPPED",
|
||||
"skipReason": "결정론적 command analysis에서 shell block 0 · finding 0으로 판정되어 이 역할이 필요하지 않다.",
|
||||
"skillEcho": "",
|
||||
"skillRevision": null,
|
||||
"notes": "initial/final analysis artifact로 shell/CLI가 없음을 확인했다.",
|
||||
"artifact": null
|
||||
},
|
||||
"editor": {
|
||||
"runBy": "command-pedagogy-editor",
|
||||
"skill": "writing-practitioner-guides",
|
||||
"status": "SKIPPED",
|
||||
"skipReason": "결정론적 command analysis에서 shell block 0 · finding 0으로 판정되어 이 역할이 필요하지 않다.",
|
||||
"skillEcho": "",
|
||||
"skillRevision": null,
|
||||
"notes": "initial/final analysis artifact로 shell/CLI가 없음을 확인했다.",
|
||||
"artifact": null
|
||||
},
|
||||
"reviewer": {
|
||||
"runBy": "command-pedagogy-reviewer",
|
||||
"skill": "writing-practitioner-guides",
|
||||
"status": "SKIPPED",
|
||||
"skipReason": "결정론적 command analysis에서 shell block 0 · finding 0으로 판정되어 이 역할이 필요하지 않다.",
|
||||
"skillEcho": "",
|
||||
"skillRevision": null,
|
||||
"verdict": null,
|
||||
"notes": "initial/final analysis artifact로 shell/CLI가 없음을 확인했다.",
|
||||
"artifact": null,
|
||||
"sourceSha256": null
|
||||
}
|
||||
},
|
||||
"technicalEvidence": {
|
||||
"runBy": "fact-reviewer",
|
||||
"status": "DONE",
|
||||
"skipReason": "",
|
||||
"verdict": "PASS",
|
||||
"notes": "현재 최종 파일 hash를 기준으로 SSOT/tree/local evidence를 재대조했다. live source reconciliation = UNVERIFIABLE: /home/donghyeon/workspace/keycloak-pattern 이 현재 머신에 없다. 별도 Agent tool은 노출되지 않아 current remediation 세션이 fact-reviewer 계약을 직접 수행했다.",
|
||||
"sourceSha256": "079548c3c6511e9c6878e0409cb6407266148a21593bbc469bb94819382f8547",
|
||||
"artifact": {
|
||||
"path": "runs/keycloak/2026-09-19-1928-remediation-07-concept-idp-brokering/stage/quality/technical-evidence-review.json",
|
||||
"sha256": "4ae583fb48d1d4d038236d6066497eb1ba591718e07fa6433d7ba5b7a2333704"
|
||||
}
|
||||
}
|
||||
},
|
||||
"riders": [],
|
||||
"sessions": [
|
||||
{
|
||||
"session": "chatgpt-current-remediation",
|
||||
"openedAt": "2026-09-19T10:28:06+00:00"
|
||||
},
|
||||
{
|
||||
"session": "chatgpt-current-remediation",
|
||||
"stage": "S3",
|
||||
"generation": 1,
|
||||
"beganAt": "2026-09-19T10:28:06+00:00"
|
||||
},
|
||||
{
|
||||
"session": "chatgpt-current-remediation",
|
||||
"stage": "S4",
|
||||
"generation": 1,
|
||||
"beganAt": "2026-09-19T10:28:07+00:00"
|
||||
},
|
||||
{
|
||||
"session": "chatgpt-current-remediation",
|
||||
"stage": "S5",
|
||||
"generation": 1,
|
||||
"beganAt": "2026-09-19T10:28:08+00:00"
|
||||
},
|
||||
{
|
||||
"session": "chatgpt-current-remediation",
|
||||
"stage": "S6",
|
||||
"generation": 1,
|
||||
"beganAt": "2026-09-19T10:28:08+00:00"
|
||||
}
|
||||
],
|
||||
"revision": 29,
|
||||
"updatedAt": "2026-09-19T10:28:09+00:00",
|
||||
"executionEnvironment": {
|
||||
"mode": "current-remediation-contract-replay",
|
||||
"session": "chatgpt-current-remediation",
|
||||
"agentToolAvailable": false,
|
||||
"note": "별도 Agent(subagent_type) 실행 도구가 현재 ChatGPT/Coka 환경에 노출되지 않았다. current remediation 세션이 .claude/agents 역할 계약과 각 SKILL.md를 읽고 동일한 gate를 현재 파일에 직접 실행했다. runBy는 verifier 계약 역할명이며 별도 Agent 프로세스 실행을 주장하지 않는다."
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"authority": "deterministic",
|
||||
"gate": "command-pedagogy-signals",
|
||||
"section_id": "docs/keycloak/tech-log-studio/oauth-oidc-auth-boundary/concept/concept-idp-brokering.md",
|
||||
"mode": "reference",
|
||||
"source_sha256": "079548c3c6511e9c6878e0409cb6407266148a21593bbc469bb94819382f8547",
|
||||
"result": "PASS",
|
||||
"requires_editor": false,
|
||||
"blocks": [],
|
||||
"findings": [],
|
||||
"extensions": {
|
||||
"command_like_text_blocks": []
|
||||
}
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
---
|
||||
id: d99fdec9-fe9e-4e0f-a50b-6fb9b9ed5719
|
||||
kind: CONCEPT
|
||||
slug: idp-brokering
|
||||
title: 외부 IdP Brokering의 동작
|
||||
topic: oauth-oidc-auth-boundary
|
||||
topicName: OAuth/OIDC 인증 경계
|
||||
project: KeyCloak Patterns
|
||||
status: 게시 전
|
||||
version: 4
|
||||
basisVersion: Keycloak 26.7.0 identity brokering
|
||||
studio: "https://hyeonworks.com/studio/documents/d99fdec9-fe9e-4e0f-a50b-6fb9b9ed5719/edit"
|
||||
assets:
|
||||
- key: idp-broker-upstream-downstream-boundary
|
||||
file: ../../../final/assets/idp-broker-upstream-downstream-boundary/idp-broker-upstream-downstream-boundary.svg
|
||||
sourceRevision: keycloak-patterns-lab@2026-08
|
||||
source:
|
||||
- final/document.md#선택이-코드와-흐름에-반영되는-방식-google-login
|
||||
---
|
||||
|
||||
# 외부 IdP Brokering의 동작
|
||||
|
||||
브로커링(brokering)은 브로커가 외부 IdP(Identity Provider)의 인증 결과를 대신 받아 검증하고, 자기 realm 안의 사용자와 연결하는 동작이다. 연결이 끝나면 브로커는 자기가 만든 authorization code를 애플리케이션으로 보낸다. 애플리케이션이 받는 코드와 토큰은 언제나 브로커가 발급한 것이라, 외부 IdP를 붙여도 애플리케이션이 상대하는 issuer, 곧 그 토큰을 발급한 주체는 바뀌지 않는다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **외부 IdP 연동과 Application 인증 구조의 경계**
|
||||
이 동작을 경계 기준으로 정리한 기록이다.
|
||||
- **OAuth Token과 Application Session을 구분하는 기준**
|
||||
외부 IdP 세션과 애플리케이션 상태를 구분하는 기준이다.
|
||||
- **Authorization Code Flow의 Endpoint와 Credential 이동 기준**
|
||||
브로커가 발급하는 코드가 지나는 endpoint다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
## 두 개의 OAuth 왕복이 이어진다
|
||||
|
||||
사용자가 브로커 로그인 화면에서 어느 외부 IdP로 로그인할지 고르면 인증 왕복이 두 번 일어난다. 앞의 왕복은 브로커와 외부 IdP 사이에서, 뒤의 왕복은 애플리케이션과 브로커 사이에서 일어난다. 브로커보다 앞에 있는 외부 IdP 쪽을 upstream이라고 부른다.
|
||||
|
||||
먼저 브라우저가 외부 IdP에 authorization 요청을 보내고, 브로커는 돌아온 응답을 검증해 자기 realm의 사용자와 연결한다. 그다음 애플리케이션으로 나가는 값은 브로커가 다시 만든다. Upstream IdP와 애플리케이션 경계는 다음처럼 이어진다.
|
||||
|
||||
:::evidence key="idp-broker-upstream-downstream-boundary" alt="Upstream IdP의 identity assertion이 Keycloak broker에서 local identity와 Keycloak authorization code로 바뀐 뒤 기존 AP1·AP2·AP3·AP4 경계로 이어지는 흐름." caption=" " zoom="true"
|
||||
:::
|
||||
|
||||
외부 IdP가 보낸 것은 첫 줄의 `Google identity assertion` 하나이고, 그 아래 `Keycloak local user/session`과 `Keycloak authorization code`는 브로커가 만든다.
|
||||
|
||||
## 애플리케이션이 상대하는 issuer는 그대로다
|
||||
|
||||
AP1의 Resource Server가 검증하는 issuer도 브로커이고, AP2와 AP3가 주고받는 authorization code의 issuer도 브로커이며, AP4의 oauth2-proxy가 OIDC(OpenID Connect) 공급자로 연결하는 곳도 브로커다. 외부 IdP가 발급한 토큰은 애플리케이션까지 내려가지 않는다.
|
||||
|
||||
| 계층 | 무엇을 발급하나 | 누가 검증하나 |
|
||||
|---|---|---|
|
||||
| 외부 IdP | upstream identity assertion | 브로커 |
|
||||
| 브로커 | authorization code, access·ID token | 애플리케이션과 Resource Server |
|
||||
|
||||
그래서 소셜 로그인을 붙여도 브라우저가 토큰을 받는지, 어느 계층이 API를 부르는지는 달라지지 않는다. 그 둘은 AP1부터 AP4까지 네 패턴 중 무엇을 골랐는지가 정한다.
|
||||
|
||||
## account identity를 정하는 key
|
||||
|
||||
브로커가 외부 IdP의 사용자를 자기 realm의 사용자와 연결할 때 쓰는 안정적인 키는 provider alias와 upstream `sub`를 묶은 값이다. alias는 브로커에 등록한 외부 IdP마다 붙인 이름이고, `sub`는 그 IdP가 사용자 한 명에게 부여하는 고유 식별자다.
|
||||
|
||||
이메일은 이 키가 아니다. 외부 IdP가 보낸 이메일이 기존 계정과 같다는 이유만으로 자동 연결하면, 그 이메일의 소유권을 증명하지 않은 채로 계정이 합쳐지기 때문이다. 계정 연결은 인증 구조와 떼어서 따로 설계할 항목이다.
|
||||
|
||||
## 경계를 섞으면 생기는 일
|
||||
|
||||
외부 IdP 연동을 다섯 번째 애플리케이션 인증 구조로 세면 upstream IdP 경계와 애플리케이션 OAuth 경계를 같은 기준으로 묶게 되는데, 두 경계는 검증 방법이 다르다. 그러면 비교표에 성격이 다른 항목이 끼어들고, 계정 연결 규칙도 인증 구조 이야기에 섞여서 따로 설계하지 않고 넘어가게 된다.
|
||||
|
||||
화면에서 어느 외부 IdP로 로그인할지 고르게 하거나 IdP마다 계정 연결을 다루는 것은 브로커가 하는 일이라 경계를 넘지 않는다. Resource Server의 토큰 검증이나 애플리케이션 인가가 외부 IdP별로 갈리기 시작하면, 브로커 경계가 애플리케이션까지 샜는지 확인한다. 외부 IdP의 토큰을 애플리케이션이 직접 받아 검증하는 경로를 만들면 브로커가 하던 계정 연결과 정책 판단도 함께 빠진다.
|
||||
|
||||
## 현재 검증한 범위
|
||||
|
||||
지금 자동화는 실제 Google 대신 controllable mock OIDC provider를 세워 브로커와 claim mapping 계약을 확인하도록 작성되어 있다. 실제 Google 계정과 공개 HTTPS redirect가 성공하는지는 증명하지 않았다. 사용자 동의와 운영 도메인 정책을 통과했다는 뜻도 아니다.
|
||||
|
||||
그래서 upstream IdP를 어디까지 검증했는지와 애플리케이션이 다루는 자격 증명 경계는 따로 적는다.
|
||||
|
||||
<!-- body:end -->
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
+78
@@ -0,0 +1,78 @@
|
||||
---
|
||||
id: d99fdec9-fe9e-4e0f-a50b-6fb9b9ed5719
|
||||
kind: CONCEPT
|
||||
slug: idp-brokering
|
||||
title: 외부 IdP Brokering의 동작
|
||||
topic: oauth-oidc-auth-boundary
|
||||
topicName: OAuth/OIDC 인증 경계
|
||||
project: KeyCloak Patterns
|
||||
status: 게시 전
|
||||
version: 4
|
||||
basisVersion: Keycloak 26.7.0 identity brokering
|
||||
studio: "https://hyeonworks.com/studio/documents/d99fdec9-fe9e-4e0f-a50b-6fb9b9ed5719/edit"
|
||||
assets:
|
||||
- key: idp-broker-upstream-downstream-boundary
|
||||
file: ../../../final/assets/idp-broker-upstream-downstream-boundary/idp-broker-upstream-downstream-boundary.svg
|
||||
sourceRevision: keycloak-patterns-lab@2026-08
|
||||
source:
|
||||
- final/document.md#선택이-코드와-흐름에-반영되는-방식-google-login
|
||||
---
|
||||
|
||||
# 외부 IdP Brokering의 동작
|
||||
|
||||
브로커링(brokering)은 브로커가 외부 IdP(Identity Provider)의 인증 결과를 대신 받아 검증하고, 자기 realm 안의 사용자와 연결하는 동작이다. 연결이 끝나면 브로커는 자기가 만든 authorization code를 애플리케이션으로 보낸다. 애플리케이션이 받는 코드와 토큰은 언제나 브로커가 발급한 것이라, 외부 IdP를 붙여도 애플리케이션이 상대하는 issuer, 곧 그 토큰을 발급한 주체는 바뀌지 않는다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **외부 IdP 연동과 Application 인증 구조의 경계**
|
||||
이 동작을 경계 기준으로 정리한 기록이다.
|
||||
- **OAuth Token과 Application Session을 구분하는 기준**
|
||||
외부 IdP 세션과 애플리케이션 상태를 구분하는 기준이다.
|
||||
- **Authorization Code Flow의 Endpoint와 Credential 이동 기준**
|
||||
브로커가 발급하는 코드가 지나는 endpoint다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
## 두 개의 OAuth 왕복이 이어진다
|
||||
|
||||
사용자가 브로커 로그인 화면에서 어느 외부 IdP로 로그인할지 고르면 인증 왕복이 두 번 일어난다. 앞의 왕복은 브로커와 외부 IdP 사이에서, 뒤의 왕복은 애플리케이션과 브로커 사이에서 일어난다. 브로커보다 앞에 있는 외부 IdP 쪽을 upstream이라고 부른다.
|
||||
|
||||
먼저 브라우저가 외부 IdP에 authorization 요청을 보내고, 브로커는 돌아온 응답을 검증해 자기 realm의 사용자와 연결한다. 그다음 애플리케이션으로 나가는 값은 브로커가 다시 만든다. Upstream IdP와 애플리케이션 경계는 다음처럼 이어진다.
|
||||
|
||||
:::evidence key="idp-broker-upstream-downstream-boundary" alt="Upstream IdP의 identity assertion이 Keycloak broker에서 local identity와 Keycloak authorization code로 바뀐 뒤 기존 AP1·AP2·AP3·AP4 경계로 이어지는 흐름." caption=" " zoom="true"
|
||||
:::
|
||||
|
||||
외부 IdP가 보낸 것은 첫 줄의 `Google identity assertion` 하나이고, 그 아래 `Keycloak local user/session`과 `Keycloak authorization code`는 브로커가 만든다.
|
||||
|
||||
## 애플리케이션이 상대하는 issuer는 그대로다
|
||||
|
||||
AP1의 Resource Server가 검증하는 issuer도 브로커이고, AP2와 AP3가 주고받는 authorization code의 issuer도 브로커이며, AP4의 oauth2-proxy가 OIDC(OpenID Connect) 공급자로 연결하는 곳도 브로커다. 외부 IdP가 발급한 토큰은 애플리케이션까지 내려가지 않는다.
|
||||
|
||||
| 계층 | 무엇을 발급하나 | 누가 검증하나 |
|
||||
|---|---|---|
|
||||
| 외부 IdP | upstream identity assertion | 브로커 |
|
||||
| 브로커 | authorization code, access·ID token | 애플리케이션과 Resource Server |
|
||||
|
||||
그래서 소셜 로그인을 붙여도 브라우저가 토큰을 받는지, 어느 계층이 API를 부르는지는 달라지지 않는다. 그 둘은 AP1부터 AP4까지 네 패턴 중 무엇을 골랐는지가 정한다.
|
||||
|
||||
## account identity를 정하는 key
|
||||
|
||||
브로커가 외부 IdP의 사용자를 자기 realm의 사용자와 연결할 때 쓰는 안정적인 키는 provider alias와 upstream `sub`를 묶은 값이다. alias는 브로커에 등록한 외부 IdP마다 붙인 이름이고, `sub`는 그 IdP가 사용자 한 명에게 부여하는 고유 식별자다.
|
||||
|
||||
이메일은 이 키가 아니다. 외부 IdP가 보낸 이메일이 기존 계정과 같다는 이유만으로 자동 연결하면, 그 이메일의 소유권을 증명하지 않은 채로 계정이 합쳐지기 때문이다. 계정 연결은 인증 구조와 떼어서 따로 설계할 항목이다.
|
||||
|
||||
## 경계를 섞으면 생기는 일
|
||||
|
||||
외부 IdP 연동을 다섯 번째 애플리케이션 인증 구조로 세면 upstream IdP 경계와 애플리케이션 OAuth 경계를 같은 기준으로 묶게 되는데, 두 경계는 검증 방법이 다르다. 그러면 비교표에 성격이 다른 항목이 끼어들고, 계정 연결 규칙도 인증 구조 이야기에 섞여서 따로 설계하지 않고 넘어가게 된다.
|
||||
|
||||
화면에서 어느 외부 IdP로 로그인할지 고르게 하거나 IdP마다 계정 연결을 다루는 것은 브로커가 하는 일이라 경계를 넘지 않는다. Resource Server의 토큰 검증이나 애플리케이션 인가가 외부 IdP별로 갈리기 시작하면, 브로커 경계가 애플리케이션까지 샜는지 확인한다. 외부 IdP의 토큰을 애플리케이션이 직접 받아 검증하는 경로를 만들면 브로커가 하던 계정 연결과 정책 판단도 함께 빠진다.
|
||||
|
||||
## 현재 검증한 범위
|
||||
|
||||
지금 자동화는 실제 Google 대신 controllable mock OIDC provider를 세워 브로커와 claim mapping 계약을 확인하도록 작성되어 있다. 실제 Google 계정과 공개 HTTPS redirect가 성공하는지는 증명하지 않았다. 사용자 동의와 운영 도메인 정책을 통과했다는 뜻도 아니다.
|
||||
|
||||
그래서 upstream IdP를 어디까지 검증했는지와 애플리케이션이 다루는 자격 증명 경계는 따로 적는다.
|
||||
|
||||
<!-- body:end -->
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"authority": "deterministic",
|
||||
"gate": "command-pedagogy-signals",
|
||||
"section_id": "docs/keycloak/tech-log-studio/oauth-oidc-auth-boundary/concept/concept-idp-brokering.md",
|
||||
"mode": "reference",
|
||||
"source_sha256": "079548c3c6511e9c6878e0409cb6407266148a21593bbc469bb94819382f8547",
|
||||
"result": "PASS",
|
||||
"requires_editor": false,
|
||||
"blocks": [],
|
||||
"findings": [],
|
||||
"extensions": {
|
||||
"command_like_text_blocks": []
|
||||
}
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
---
|
||||
id: d99fdec9-fe9e-4e0f-a50b-6fb9b9ed5719
|
||||
kind: CONCEPT
|
||||
slug: idp-brokering
|
||||
title: 외부 IdP Brokering의 동작
|
||||
topic: oauth-oidc-auth-boundary
|
||||
topicName: OAuth/OIDC 인증 경계
|
||||
project: KeyCloak Patterns
|
||||
status: 게시 전
|
||||
version: 4
|
||||
basisVersion: Keycloak 26.7.0 identity brokering
|
||||
studio: "https://hyeonworks.com/studio/documents/d99fdec9-fe9e-4e0f-a50b-6fb9b9ed5719/edit"
|
||||
assets:
|
||||
- key: idp-broker-upstream-downstream-boundary
|
||||
file: ../../../final/assets/idp-broker-upstream-downstream-boundary/idp-broker-upstream-downstream-boundary.svg
|
||||
sourceRevision: keycloak-patterns-lab@2026-08
|
||||
source:
|
||||
- final/document.md#선택이-코드와-흐름에-반영되는-방식-google-login
|
||||
---
|
||||
|
||||
# 외부 IdP Brokering의 동작
|
||||
|
||||
브로커링(brokering)은 브로커가 외부 IdP(Identity Provider)의 인증 결과를 대신 받아 검증하고, 자기 realm 안의 사용자와 연결하는 동작이다. 연결이 끝나면 브로커는 자기가 만든 authorization code를 애플리케이션으로 보낸다. 애플리케이션이 받는 코드와 토큰은 언제나 브로커가 발급한 것이라, 외부 IdP를 붙여도 애플리케이션이 상대하는 issuer, 곧 그 토큰을 발급한 주체는 바뀌지 않는다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **외부 IdP 연동과 Application 인증 구조의 경계**
|
||||
이 동작을 경계 기준으로 정리한 기록이다.
|
||||
- **OAuth Token과 Application Session을 구분하는 기준**
|
||||
외부 IdP 세션과 애플리케이션 상태를 구분하는 기준이다.
|
||||
- **Authorization Code Flow의 Endpoint와 Credential 이동 기준**
|
||||
브로커가 발급하는 코드가 지나는 endpoint다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
## 두 개의 OAuth 왕복이 이어진다
|
||||
|
||||
사용자가 브로커 로그인 화면에서 어느 외부 IdP로 로그인할지 고르면 인증 왕복이 두 번 일어난다. 앞의 왕복은 브로커와 외부 IdP 사이에서, 뒤의 왕복은 애플리케이션과 브로커 사이에서 일어난다. 브로커보다 앞에 있는 외부 IdP 쪽을 upstream이라고 부른다.
|
||||
|
||||
먼저 브라우저가 외부 IdP에 authorization 요청을 보내고, 브로커는 돌아온 응답을 검증해 자기 realm의 사용자와 연결한다. 그다음 애플리케이션으로 나가는 값은 브로커가 다시 만든다. Upstream IdP와 애플리케이션 경계는 다음처럼 이어진다.
|
||||
|
||||
:::evidence key="idp-broker-upstream-downstream-boundary" alt="Upstream IdP의 identity assertion이 Keycloak broker에서 local identity와 Keycloak authorization code로 바뀐 뒤 기존 AP1·AP2·AP3·AP4 경계로 이어지는 흐름." caption=" " zoom="true"
|
||||
:::
|
||||
|
||||
외부 IdP가 보낸 것은 첫 줄의 `Google identity assertion` 하나이고, 그 아래 `Keycloak local user/session`과 `Keycloak authorization code`는 브로커가 만든다.
|
||||
|
||||
## 애플리케이션이 상대하는 issuer는 그대로다
|
||||
|
||||
AP1의 Resource Server가 검증하는 issuer도 브로커이고, AP2와 AP3가 주고받는 authorization code의 issuer도 브로커이며, AP4의 oauth2-proxy가 OIDC(OpenID Connect) 공급자로 연결하는 곳도 브로커다. 외부 IdP가 발급한 토큰은 애플리케이션까지 내려가지 않는다.
|
||||
|
||||
| 계층 | 무엇을 발급하나 | 누가 검증하나 |
|
||||
|---|---|---|
|
||||
| 외부 IdP | upstream identity assertion | 브로커 |
|
||||
| 브로커 | authorization code, access·ID token | 애플리케이션과 Resource Server |
|
||||
|
||||
그래서 소셜 로그인을 붙여도 브라우저가 토큰을 받는지, 어느 계층이 API를 부르는지는 달라지지 않는다. 그 둘은 AP1부터 AP4까지 네 패턴 중 무엇을 골랐는지가 정한다.
|
||||
|
||||
## account identity를 정하는 key
|
||||
|
||||
브로커가 외부 IdP의 사용자를 자기 realm의 사용자와 연결할 때 쓰는 안정적인 키는 provider alias와 upstream `sub`를 묶은 값이다. alias는 브로커에 등록한 외부 IdP마다 붙인 이름이고, `sub`는 그 IdP가 사용자 한 명에게 부여하는 고유 식별자다.
|
||||
|
||||
이메일은 이 키가 아니다. 외부 IdP가 보낸 이메일이 기존 계정과 같다는 이유만으로 자동 연결하면, 그 이메일의 소유권을 증명하지 않은 채로 계정이 합쳐지기 때문이다. 계정 연결은 인증 구조와 떼어서 따로 설계할 항목이다.
|
||||
|
||||
## 경계를 섞으면 생기는 일
|
||||
|
||||
외부 IdP 연동을 다섯 번째 애플리케이션 인증 구조로 세면 upstream IdP 경계와 애플리케이션 OAuth 경계를 같은 기준으로 묶게 되는데, 두 경계는 검증 방법이 다르다. 그러면 비교표에 성격이 다른 항목이 끼어들고, 계정 연결 규칙도 인증 구조 이야기에 섞여서 따로 설계하지 않고 넘어가게 된다.
|
||||
|
||||
화면에서 어느 외부 IdP로 로그인할지 고르게 하거나 IdP마다 계정 연결을 다루는 것은 브로커가 하는 일이라 경계를 넘지 않는다. Resource Server의 토큰 검증이나 애플리케이션 인가가 외부 IdP별로 갈리기 시작하면, 브로커 경계가 애플리케이션까지 샜는지 확인한다. 외부 IdP의 토큰을 애플리케이션이 직접 받아 검증하는 경로를 만들면 브로커가 하던 계정 연결과 정책 판단도 함께 빠진다.
|
||||
|
||||
## 현재 검증한 범위
|
||||
|
||||
지금 자동화는 실제 Google 대신 controllable mock OIDC provider를 세워 브로커와 claim mapping 계약을 확인하도록 작성되어 있다. 실제 Google 계정과 공개 HTTPS redirect가 성공하는지는 증명하지 않았다. 사용자 동의와 운영 도메인 정책을 통과했다는 뜻도 아니다.
|
||||
|
||||
그래서 upstream IdP를 어디까지 검증했는지와 애플리케이션이 다루는 자격 증명 경계는 따로 적는다.
|
||||
|
||||
<!-- body:end -->
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"record": "docs/keycloak/tech-log-studio/oauth-oidc-auth-boundary/concept/concept-idp-brokering.md",
|
||||
"sourceSha256": "079548c3c6511e9c6878e0409cb6407266148a21593bbc469bb94819382f8547",
|
||||
"verdict": "PASS",
|
||||
"checks": [
|
||||
{
|
||||
"name": "required-content",
|
||||
"cmd": "python3 scripts/check-required-content.py --file docs/keycloak/tech-log-studio/oauth-oidc-auth-boundary/concept/concept-idp-brokering.md",
|
||||
"exit": 0
|
||||
},
|
||||
{
|
||||
"name": "tree",
|
||||
"cmd": "python3 scripts/verify-tech-log-tree.py keycloak",
|
||||
"exit": 0
|
||||
},
|
||||
{
|
||||
"name": "evidence-local",
|
||||
"cmd": "node .agents/skills/writing-tech-log-records/scripts/check_evidence.mjs keycloak",
|
||||
"exit": 0
|
||||
}
|
||||
],
|
||||
"liveSourceReconciliation": "UNVERIFIABLE",
|
||||
"liveSourcePath": "/home/donghyeon/workspace/keycloak-pattern",
|
||||
"note": "현재 최종 Record를 기존 SSOT/tree/local evidence 계약에 재대조했다. source repository가 없으면 live reconciliation은 UNVERIFIABLE로 남긴다."
|
||||
}
|
||||
Reference in New Issue
Block a user