--- title: "Extract roles from access token issued by Keycloak using Spring Security (Between Data / Christian Huff)" source_type: personal-blog url: https://betweendata.io/posts/secure-spring-rest-api-using-keycloak/ archive_url: related_branches: [feature-keycloak-spring-rs-role-mapping] related_projects: [keycloak-patterns] tags: [personal-blog, keycloak-patterns, auth, spring-security, keycloak] status: raw confidence: medium created: 2026-07-18 last_reviewed: 2026-07-18 --- # Extract roles from access token issued by Keycloak using Spring Security (Between Data / Christian Huff) > Layer: `raw/company-tech-blogs/` (분류: 실제 `source_type` 은 `personal-blog` — 저자 Christian Huff 개인 블로그. 저장소 기존 관행([[raw/company-tech-blogs/test-pyramid-vs-trophy-kent-dodds]], `senior-engineer-competency-mubin-shaikh.md`, `deliberate-practice-software-developers-redgreencode.md`)에 따라 `company-tech-blogs/` 디렉토리에 위치하되 frontmatter `source_type: personal-blog` 유지). > 회사 기술 블로그가 아니므로 **공식 best practice 로 격상 금지** (CLAUDE.md §5, §11). 아래 모든 Claim 은 `engineering-blog` 강도 — 개인 저자의 구현 사례일 뿐, Spring/Keycloak 공식 권고가 아니다. ## Parent / 활용 branch (필수) | Branch | 이 자료가 정당화하는 결정 | |---|---| | [[raw/branch-notes/feature-keycloak-spring-rs-role-mapping]] | Keycloak realm role 을 Spring `GrantedAuthority` 로 매핑하는 구현 방식 — 손으로 작성한 `Converter>` 가 nested `realm_access` claim 을 읽어 `ROLE_` prefix 붙은 authority 로 변환하고, 필요 시 `DelegatingJwtGrantedAuthoritiesConverter` 로 default scope converter 와 결합하는 접근의 **사례(case study) 근거**. `feature-keycloak-spring-rs-role-mapping` D4 (`realm role 만 매핑`) 의 구현 detail 참고 자료. ## 출처 / Source - 원본 URL: https://betweendata.io/posts/secure-spring-rest-api-using-keycloak/ - 아카이브 URL: (미수집) - 저자 / 조직: Christian Huff (개인 블로그 "Between Data") - 발행일: 2023-02-23 - 마지막 확인일: 2026-07-18 ## 왜 저장했는지 / Why archived Spring Boot 3 + `spring-boot-starter-oauth2-resource-server` 환경에서 Keycloak 이 발급한 JWT 의 `realm_access`/`resource_access` claim 은 Spring 의 기본 `JwtGrantedAuthoritiesConverter` 가 자동으로 추출하지 못한다. 이 자료는 그 문제를 **커스텀 `Converter>`** 로 해결한 구체 코드 사례를 담고 있어, `feature-keycloak-spring-rs-role-mapping` 의 role mapping 구현 detail 을 정당화하는 참고 사례로 보관. ## 핵심 인용 / Key quotes (verbatim) > [§Extract Roles from Access Token — class declaration] "public class KeycloakJwtRolesConverter implements Converter> {" > [§Extract Roles from Access Token — realm_access claim 이름 정의 + 실제 읽기] "private static final String CLAIM_REALM_ACCESS = "realm_access";" [...] "Map> realmAccess = jwt.getClaim(CLAIM_REALM_ACCESS);" > [§Extract Roles from Access Token — ROLE_ prefix 상수] "public static final String PREFIX_REALM_ROLE = "ROLE_realm_";" [...] "public static final String PREFIX_RESOURCE_ROLE = "ROLE_";" > [§Extract Roles from Access Token — ROLE_ prefix 설명 (본문)] "In the returned authorities the realm roles are prefixed with ROLE_realm_ while the resource roles are prefixed with ROLE_[NAME_OF_THE_RESOURCE]_." > [§Define Access Rules — DelegatingJwtGrantedAuthoritiesConverter 조합] "new DelegatingJwtGrantedAuthoritiesConverter(" [...] "new JwtGrantedAuthoritiesConverter()," [...] "new KeycloakJwtRolesConverter());" ## Claims Extracted / 추출된 주장 | Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove | |---|---|---|---|---|---| | KC-ROLE-BD-C1 | 저자는 `Converter>` 를 구현하는 `KeycloakJwtRolesConverter` 클래스를 작성해, `realm_access` claim 이름을 상수로 정의하고 `jwt.getClaim(CLAIM_REALM_ACCESS)` 로 직접 읽는다 | "public class KeycloakJwtRolesConverter implements Converter> {" / "private static final String CLAIM_REALM_ACCESS = \"realm_access\";" / "Map> realmAccess = jwt.getClaim(CLAIM_REALM_ACCESS);" | `engineering-blog` | Spring Boot 3 + Spring Security OAuth2 Resource Server 환경에서 Keycloak `realm_access` (nested claim map) 을 `GrantedAuthority` 로 변환하는 구현 패턴 | 이 방식이 Spring 또는 Keycloak 공식 권고 패턴이라는 것은 아님 (원문에 공식 문서 인용 없음). Keycloak 모든 버전에서 `realm_access` claim 구조가 동일하다는 보증도 아님 — 원문 예시 토큰은 특정 시점(2023-02) Keycloak 버전 기준 | | KC-ROLE-BD-C2 | realm-level role 은 `ROLE_realm_` prefix, resource(client)-level role 은 `ROLE_[리소스명]_` prefix 를 붙여 `SimpleGrantedAuthority` 로 변환한다고 명시 | "public static final String PREFIX_REALM_ROLE = \"ROLE_realm_\";" / "public static final String PREFIX_RESOURCE_ROLE = \"ROLE_\";" / "In the returned authorities the realm roles are prefixed with ROLE_realm_ while the resource roles are prefixed with ROLE_[NAME_OF_THE_RESOURCE]_." | `engineering-blog` | Spring Security `hasAuthority(...)` 매칭을 위한 authority 명명 규칙의 한 예시(개인 저자 관례) | `ROLE_` prefix 가 Spring Security 의 필수 요구사항이라는 것은 아님 — `hasAuthority` 는 임의 문자열 매칭이 가능하고, `ROLE_` prefix 규칙은 `hasRole(...)` 사용 시에만 Spring 이 자동으로 붙이는 것과는 다른 맥락(원문은 이 구분을 설명하지 않음) | | KC-ROLE-BD-C3 | `WebSecurityConfiguration.filterChain(...)` 에서 `DelegatingJwtGrantedAuthoritiesConverter` 를 사용해 default `JwtGrantedAuthoritiesConverter` 와 커스텀 `KeycloakJwtRolesConverter` 를 함께 등록한다 | "new DelegatingJwtGrantedAuthoritiesConverter(" [...] "new JwtGrantedAuthoritiesConverter()," [...] "new KeycloakJwtRolesConverter());" | `engineering-blog` | scope 기반 default authority 와 realm/resource role 기반 custom authority를 하나의 authorities 집합으로 합치는 조합 패턴의 사례 | 이 조합이 모든 프로젝트에 필요하다는 것은 아님 — scope 기반 인가를 병행하지 않는 프로젝트라면 default converter 생략 가능. 원문도 코드 주석 수준("Using the delegating converter multiple converters can be combined")의 설명만 제공하며 `DelegatingJwtGrantedAuthoritiesConverter` API 계약 자체의 공식 문서화는 아님 | ### 참고: 이 raw 는 `engineering-blog` 강도만 제공 — official 보강 필요 `KC-ROLE-BD-C1`~`C3` 는 모두 `engineering-blog` (개인 저자 사례). branch-note 에서 이를 "공식 best practice" 로 인용하면 안 된다 (CLAUDE.md §5, §11). `realm_access` 가 default `JwtGrantedAuthoritiesConverter` 로 자동 매핑되지 않는다는 사실 자체의 공식 근거가 필요하면 [[raw/official-docs/spring-security-resource-server-jwt]] (예: 기존 branch-note 인용 `SSRS-JWT-C4` — default converter 는 `scope`/`scp` 만 `SCOPE_` prefix 로 자동 변환) 를 함께 인용해야 `official-vendor-doc` 급 근거가 된다. 이 raw 단독으로는 D4(realm role 만 매핑) 의 "왜 커스텀 컨버터가 필요한가"에 대한 **사례**일 뿐, "Spring 이 이렇게 하라고 권고한다"는 근거는 아니다. ## Usage Boundaries / 적용 경계 - **이 자료가 직접 증명하는 것**: - `KC-ROLE-BD-C1`: 손으로 작성한 `Converter>` 구현이 `realm_access` claim 을 nested map 으로 읽어올 수 있다는 동작 사례 (저자 GitHub 리포지토리에 테스트 100% 커버리지 존재한다고 원문이 주장 — 코드 자체는 미검증) - `KC-ROLE-BD-C2`: `ROLE_realm_` / `ROLE_[resource]_` prefix 부여 방식 예시 - `KC-ROLE-BD-C3`: `DelegatingJwtGrantedAuthoritiesConverter` 로 default + custom converter 를 합치는 코드 구조 예시 - **이 자료가 증명하지 않는 것**: - 이 구현이 Spring Security 또는 Keycloak 의 공식 권장 패턴이라는 명제 — 원문은 개인 저자의 "minimally invasive" 선택 설명일 뿐, RFC/공식 문서 인용 없음 - `realm_access.roles` 매핑이 모든 Keycloak 버전·모든 client 설정에서 동일하게 동작한다는 명제 — 예시 토큰은 특정 realm/client 설정(`backend` realm, `rest-api` client) 기준 - `ROLE_` prefix 없이 `hasAuthority`/`hasRole` 을 섞어 쓸 때의 Spring Security 내부 동작 차이에 대한 설명 — 원문 미포함 - **내 프로젝트에 적용하려면 추가 확인이 필요한 것**: - `feature-keycloak-spring-rs-role-mapping` 이 실제로 `resource_access` (client-level role) 까지 매핑할지, 아니면 D4 결정대로 `realm_access` 만 매핑할지 — 이 raw 의 `KeycloakJwtRolesConverter` 는 두 claim 을 모두 처리하므로 branch 결정과 범위가 다름(branch 는 realm role만, 이 raw 는 realm+resource 모두)에 주의 - 로컬 Keycloak 인스턴스에서 발급한 access token 의 `realm_access.roles` 실제 JSON 구조가 이 raw 의 예시 토큰과 일치하는지 확인 - `DelegatingJwtGrantedAuthoritiesConverter` 조합이 `feature-keycloak-spring-rs-role-mapping` 의 범위(§구현 가이드)에 실제로 필요한지 — branch 는 `@PreAuthorize` 대신 SecurityFilterChain matcher 를 우선하기로 결정했으므로 (D5), 이 raw 의 `.requestMatchers(...).hasAuthority(...)` 패턴과의 정합 재검토 필요 ## 메모 / Notes > 검증되지 않은 내 해석은 wiki source-summary 단계에서만. - 원문은 realm-level role 과 resource(client)-level role 을 **모두** 매핑하는 구현(`KeycloakJwtRolesConverter`)을 제시하지만, `feature-keycloak-spring-rs-role-mapping` 의 D4 는 "realm role만 매핑 (resource_access 무시)"로 범위를 좁혔다 — 이 raw 를 인용할 때 **resource_access 부분은 branch 범위 밖**임을 명시해야 함 (OUT_OF_BRANCH_SCOPE 유사 주의). - 원문 저자는 Keycloak 기본 설정(mapper 미변경)을 유지하는 쪽을 "minimally invasive" 라고 표현 — 이는 branch 의 "Keycloak mapper 커스터마이징 대신 Spring 쪽 컨버터로 흡수" 방향과 같은 트레이드오프 축으로 보인다(해석, 미검증). - 저자는 GitHub 코드 링크(`ChristianHuff-DEV/secure-spring-rest-api-using-keycloak`)와 100% 테스트 커버리지를 주장하나, 이 raw 는 블로그 본문만 발췌·검증했고 GitHub 코드 자체는 self-grep 대상에 포함하지 않음 — 실제 사용 시 코드 diff 재확인 필요. ## Related / 관련 - 같은 주제 다른 raw: - [[raw/official-docs/spring-security-resource-server-jwt]] — default `JwtGrantedAuthoritiesConverter` 가 `scope`/`scp` 만 자동 매핑한다는 공식 근거 (`SSRS-JWT-C4`) — 이 raw 의 C1과 짝을 이뤄야 `official-vendor-doc` 급 근거 완성 - 인용하는 branch: - [[raw/branch-notes/feature-keycloak-spring-rs-role-mapping]] - 인용한 wiki 요약: (미작성)