728x90
이 글에서는 최신 버전인 Keycloak 26을 기준으로 JWT에 사용자가 소속된 그룹 정보를 {id, name} 형태로 추가하기 위한 Client Scope 설정 및 Custom Mapper를 구현하는 방법을 자세히 다룹니다.
1. 요구사항 정의
Keycloak에서 발급한 JWT의 Payload에 사용자가 속한 그룹의 id와 name 정보를 배열 형태로 추가합니다.
예상되는 Payload 구조:
{
"sub": "12345678-1234-1234-1234-123456789abc",
"preferred_username": "johndoe",
"group_info": [
{"id": "group-1-id", "name": "Admins"},
{"id": "group-2-id", "name": "Users"}
]
}
2. Client Scope 생성
Keycloak 관리 콘솔에서 아래 단계를 수행합니다.
- Client Scopes 메뉴로 이동 후, Create client scope 클릭
- 이름을 custom-groups-scope로 설정하고 저장
3. Custom Protocol Mapper 구현 (SPI)
Keycloak 기본 mapper로는 위 형태를 구현할 수 없으므로, Java로 커스텀 SPI를 구현합니다.
프로젝트 설정 (Gradle 예시)
plugins {
java
}
group = "com.kyoulho"
version = "1.0.0"
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
repositories {
mavenCentral()
}
dependencies {
compileOnly ("org.keycloak:keycloak-core:26.0.0")
compileOnly ("org.keycloak:keycloak-server-spi:26.0.0")
compileOnly ("org.keycloak:keycloak-services:26.0.0")
}
Mapper 구현
GroupInfoProtocolMapper.java를 생성합니다:
package com.kyoulho;
import org.keycloak.models.ClientSessionContext;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.oidc.mappers.*;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.representations.IDToken;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class GroupInfoProtocolMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, UserInfoTokenMapper {
// 매퍼 식별자
public static final String PROVIDER_ID = "group-info-protocol-mapper";
// 매퍼 설정 시 노출되는 프로퍼티 목록
private static final List<ProviderConfigProperty> CONFIG_PROPERTIES = new ArrayList<>();
static {
// 토큰 클레임 이름 등 기본 설정 추가
OIDCAttributeMapperHelper.addTokenClaimNameConfig(CONFIG_PROPERTIES);
OIDCAttributeMapperHelper.addJsonTypeConfig(CONFIG_PROPERTIES);
// 어떤 토큰(Access/ID/UserInfo)에 포함할지 설정
OIDCAttributeMapperHelper.addIncludeInTokensConfig(CONFIG_PROPERTIES, GroupInfoProtocolMapper.class);
// ✅ multivalued 설정 추가 (리스트 구조 보존용)
ProviderConfigProperty multiValuedProp = new ProviderConfigProperty();
multiValuedProp.setName("multivalued");
multiValuedProp.setLabel("Multivalued");
multiValuedProp.setType(ProviderConfigProperty.BOOLEAN_TYPE);
multiValuedProp.setDefaultValue("true");
multiValuedProp.setHelpText("If true, a list of values will remain an array in the token.");
CONFIG_PROPERTIES.add(multiValuedProp);
}
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public String getDisplayCategory() {
// 관리자 UI에서의 카테고리 이름
return TOKEN_MAPPER_CATEGORY;
}
@Override
public String getDisplayType() {
// 관리자 UI에서 이 매퍼를 선택할 때 표시될 이름
return "Group Info (ID & Name)";
}
@Override
public String getHelpText() {
return "Adds an array of {id, name} objects for each group the user is in.";
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return CONFIG_PROPERTIES;
}
/**
* 그룹 정보를 토큰에 담는 핵심 로직
*/
@Override
protected void setClaim(IDToken token,
ProtocolMapperModel mappingModel,
UserSessionModel userSession,
KeycloakSession keycloakSession,
ClientSessionContext clientSessionCtx) {
// 사용자가 속한 그룹마다 {id, name} 맵 구조 생성
List<Map<String, String>> groupInfoList = userSession.getUser().getGroupsStream()
.map(groupModel -> {
Map<String, String> groupMap = new HashMap<>();
groupMap.put("id", groupModel.getId());
groupMap.put("name", groupModel.getName());
return groupMap;
})
.collect(Collectors.toList());
// 매퍼가 지정한 클레임 이름(ex: group_info)으로 groupInfoList를 매핑
OIDCAttributeMapperHelper.mapClaim(token, mappingModel, groupInfoList);
}
}
SPI 등록
resources/META-INF/services에 org.keycloak.protocol.ProtocolMapper 파일을 생성합니다.
파일에 클래스 명을 추가합니다:
com.kyoulho.GroupInfoProtocolMapper
4. 배포 및 설정
- 작성한 모듈을 Jar로 빌드한 뒤, Keycloak의 /providers 디렉토리에 복사하고 Keycloak을 재시작합니다.
FROM gradle:8.12-jdk17 AS builder
WORKDIR /app
COPY build.gradle.kts settings.gradle.kts /app/
COPY src /app/src
RUN gradle clean build --no-daemon
FROM bitnami/keycloak:26
USER root
COPY theme /opt/bitnami/keycloak/themes/yeosu-theme
COPY --from=builder /app/build/libs/*.jar /opt/bitnami/keycloak/providers/
USER 1001
ENTRYPOINT [ "/opt/bitnami/scripts/keycloak/entrypoint.sh" ]
CMD [ "/opt/bitnami/scripts/keycloak/run.sh", "start", "--optimized" ]
5. Mapper 적용하기
- Keycloak 관리 콘솔에서 custom-groups-scope의 Mapper 유형으로 방금 생성한 Group Info (ID & Name)을 선택하고, 클레임 이름(group_info)을 설정한 후 저장합니다.
6. Client Scope 연결
- JWT에 그룹 정보를 추가하고자 하는 클라이언트의 설정에서 Client Scopes 탭으로 이동하여, 방금 만든 custom-groups-scope를 추가합니다.
728x90
'Keycloak' 카테고리의 다른 글
[Keycloak] 어드민 API 호출 권한, 사용자에게 직접 부여하기 (0) | 2025.04.01 |
---|---|
[Keycloak] Spring Boot Resource Server 연동하기 (0) | 2025.03.26 |
[Keycloak] Custom API (0) | 2025.03.04 |