{ "version": "1.1", "id": "bearer-jwt-validation-chain", "title": "Bearer JWT가 검증과 역할 변환을 지나 principal이 된다", "question": "Resource Server가 받은 Bearer JWT는 어떤 검증과 변환 단계를 지나 authenticated principal이 되는가?", "type": "data-flow", "direction": "TB", "audience": [ "Spring Security Resource Server의 JWT 검증 경계를 확인하는 개발자" ], "summary": "Bearer JWT가 JWK 서명, issuer·시간, audience 검증을 거친 뒤 realm role 변환을 통해 authenticated principal과 ROLE_* authorities가 된다.", "alt": "Bearer JWT 입력이 JwtDecoder, issuer·시간 검증, audience 검증, role converter를 거쳐 authenticated principal이 되는 검증 사슬.", "long_description": "왼쪽의 raw Bearer JWT가 NimbusJwtDecoder에서 JWK 서명을 확인한 뒤 issuer와 timestamp validator, 프로젝트의 AudienceValidator를 통과해 validated Jwt가 된다. 이어서 KeycloakRealmRoleConverter가 realm_access.roles를 ROLE_* authority로 바꾸고 authenticated principal이 만들어진다.", "source_context": { "document": "document.md", "document_sha256": "ea10df24b892e2c57123a37a4b4f0d821e4f394353e6746f50df6a48342353e9", "anchor": { "kind": "heading", "value": "AP1 완주: callback code가 브라우저 Bearer 요청이 되기까지", "line": 227 } }, "composition": { "profile": "component-flow", "diagram_only": true, "reference_ids": [ "payment-event-flow" ], "rationale": "검증 순서가 일곱 단계라 문서 폭에서 가로로 늘리지 않고 위에서 아래로 읽는 component-flow로 표현한다.", "focus_node": "audience-validator" }, "groups": [], "nodes": [ { "id": "bearer-input", "label": "Bearer JWT", "kind": "data", "role": "source", "shape": "box", "description": "Resource Server가 Authorization header에서 받은 raw Bearer JWT.", "evidence": [ { "start_line": 354, "end_line": 359 } ], "assumption": false }, { "id": "jwt-decoder", "label": "NimbusJwtDecoder", "kind": "service", "role": "service", "description": "JWK를 이용해 JWT 서명을 검증하는 decoder.", "details": [ "JWK signature" ], "evidence": [ { "start_line": 359, "end_line": 360 } ], "assumption": false }, { "id": "issuer-time", "label": "Issuer · Time validators", "kind": "service", "role": "service", "description": "Expected issuer와 timestamp 조건을 확인하는 default validators.", "evidence": [ { "start_line": 361, "end_line": 361 } ], "assumption": false }, { "id": "audience-validator", "label": "AudienceValidator", "kind": "service", "role": "service", "emphasis": "primary", "description": "keycloak-pattern-api audience 포함 여부를 확인하는 project validator.", "details": [ "keycloak-pattern-api" ], "evidence": [ { "start_line": 362, "end_line": 370 } ], "assumption": false }, { "id": "validated-jwt", "label": "Validated Jwt", "kind": "data", "role": "service", "description": "서명, issuer·시간, audience 검증을 통과한 Jwt.", "evidence": [ { "start_line": 363, "end_line": 363 } ], "assumption": false }, { "id": "role-converter", "label": "Realm role converter", "kind": "service", "role": "service", "description": "realm_access.roles 문자열에 ROLE_ prefix를 붙여 authority로 변환한다.", "details": [ "KeycloakRealmRoleConverter" ], "evidence": [ { "start_line": 364, "end_line": 370 } ], "assumption": false }, { "id": "principal", "label": "Authenticated principal", "kind": "actor", "role": "sink", "shape": "actor", "description": "검증된 JWT와 ROLE_* authorities를 가진 인증 결과.", "details": [ "ROLE_* authorities" ], "evidence": [ { "start_line": 365, "end_line": 365 } ], "assumption": false } ], "edges": [ { "id": "e1", "from": "bearer-input", "to": "jwt-decoder", "label": "decode", "kind": "data", "evidence": [ { "start_line": 354, "end_line": 360 } ], "assumption": false }, { "id": "e2", "from": "jwt-decoder", "to": "issuer-time", "label": "signature ok", "kind": "data", "evidence": [ { "start_line": 360, "end_line": 361 } ], "assumption": false }, { "id": "e3", "from": "issuer-time", "to": "audience-validator", "label": "claims", "kind": "data", "evidence": [ { "start_line": 361, "end_line": 362 } ], "assumption": false }, { "id": "e4", "from": "audience-validator", "to": "validated-jwt", "label": "valid audience", "kind": "data", "emphasis": "primary", "evidence": [ { "start_line": 362, "end_line": 363 } ], "assumption": false }, { "id": "e5", "from": "validated-jwt", "to": "role-converter", "label": "realm roles", "kind": "data", "evidence": [ { "start_line": 363, "end_line": 364 } ], "assumption": false }, { "id": "e6", "from": "role-converter", "to": "principal", "label": "authorities", "kind": "data", "evidence": [ { "start_line": 364, "end_line": 365 } ], "assumption": false } ], "legend": [], "metadata": { "rationale": "서명 검증과 API 대상 audience 검증, role 변환이 서로 다른 단계임을 한 흐름에서 구분한다." } }