Files
llm-wiki/raw/official-docs/keycloak-account-console-unlink-lockout-guard-official.md

10 KiB

title, source_type, url, archive_url, related_branches, related_projects, tags, created
title source_type url archive_url related_branches related_projects tags created
official-doc / Keycloak Account Console — Self-Service Unlink Lockout Guard (Engine Source) official-doc https://github.com/keycloak/keycloak/blob/main/services/src/main/java/org/keycloak/services/resources/account/LinkedAccountsResource.java
feature-keycloak-account-linking-sub-vs-email
keycloak-patterns
official-doc
keycloak-patterns
account-linking
keycloak
identity-brokering
2026-07-15

official-doc / Keycloak Account Console — Self-Service Unlink Lockout Guard (Engine Source)

Layer: raw/ — 외부 자료(공식 문서 / 대기업 기술 블로그)의 원문 발췌·출처 기록. 본 문서는 Keycloak 엔진 소스 코드 (LinkedAccountsResource.java) 를 공식 자료로 취급한다 — 이 lockout guard 는 narrative Admin Guide 에는 문서화되어 있지 않고 오직 소스 코드에만 존재한다.

source_type 허용값

official-doc — Keycloak 공식 레포지토리 (keycloak/keycloak, main 브랜치) 엔진 소스 코드. 벤더가 직접 배포·유지하는 코드이므로 official-vendor-doc 급 근거로 취급.

Parent / 활용 branch (필수, 최소 1개+)

Branch 이 자료가 정당화하는 결정
raw/branch-notes/feature-keycloak-account-linking-sub-vs-email D3 (Account Console self-service unlink 는 사용자가 password 설정한 경우에만 허용 — 잠금 방지) 근거. Keycloak 이 엔진 레벨에서 이 lockout 방지를 강제함을 증명 — Account REST resource 가 마지막 federated identity 제거를 HTTP 400 으로 거부한다 (federated identity 가 2개 이상이거나, LDAP-federated 이거나, password 가 설정된 경우 제외). narrative Admin Guide 에는 이 guard 가 문서화되어 있지 않고 오직 소스 코드에만 존재.

출처 / Source

왜 저장했는지 / Why archived

feature-keycloak-account-linking-sub-vs-email branch-note 의 D3 결정("self-service unlink 는 password 설정된 경우에만 허용")이 이전에는 UNSUPPORTED_DECISION 라벨로 남아 있었다 — 본 branch 의 기존 Sources(KC-FLF, GOIDC, codemancers, KC-IDP-BROKER) 어느 것도 self-service unlink 거부 메커니즘을 다루지 않았기 때문. 본 자료는 Keycloak 엔진이 실제로 이 lockout 방지를 REST 레벨에서 강제한다는 것을 소스 코드로 직접 증명하며, 이 메커니즘은 narrative Admin Guide 에 문서화되어 있지 않다.

핵심 인용 / Key quotes (verbatim, 3~5문장)

[LinkedAccountsResource.java L315] "// Removing last social provider is not possible if you don't have other possibility to authenticate"

[LinkedAccountsResource.java L316] "if (!(session.users().getFederatedIdentitiesStream(realm, user).count() > 1 || user.isFederated() || isPasswordSet())) {"

[LinkedAccountsResource.java L317] "throw ErrorResponse.error(translateErrorMessage(Messages.FEDERATED_IDENTITY_REMOVING_LAST_PROVIDER), Response.Status.BAD_REQUEST);"

[LinkedAccountsResource.java L361-362] "private boolean isPasswordSet() { return user.credentialManager().isConfiguredFor(PasswordCredentialModel.TYPE);"

[messages_en.properties L231] "federatedIdentityRemovingLastProviderMessage=You can not remove last federated identity as you do not have a password."

Claims Extracted / 추출된 주장

Claim ID Claim (이 자료가 직접 말하는 것) Evidence quote Strength Applies to Does not prove
KC-UNLINKGUARD-C1 Keycloak Account REST resource(LinkedAccountsResource#removeLinkedAccount) 는 사용자가 federated identity 를 2개 초과 보유하거나(count() > 1), LDAP 등으로 federated 되어 있거나(user.isFederated()), password 가 설정되어 있는(isPasswordSet()) 경우가 아니면 마지막 federated identity 제거 요청을 거부한다 [L315-317] "// Removing last social provider is not possible if you don't have other possibility to authenticate" / "if (!(session.users().getFederatedIdentitiesStream(realm, user).count() > 1 || user.isFederated() || isPasswordSet())) {" / "throw ErrorResponse.error(...FEDERATED_IDENTITY_REMOVING_LAST_PROVIDER..., Response.Status.BAD_REQUEST);" official-vendor-doc Keycloak Account Console (self-service DELETE /{providerAlias} REST endpoint) 의 lockout 방지 guard 존재 여부 일반 (a) 이 코드는 main 브랜치(2026-07-15 확인) 스냅샷이다 — 특정 배포 릴리즈 태그(예: 26.x)에서의 동일 동작은 별도 재확인 필요. (b) 이 guard 는 서버(엔진) 레벨 검증일 뿐이며, Account Console 프론트엔드 UX(예: unlink 버튼을 사전에 비활성화하거나 안내 메시지를 먼저 보여주는 것)를 규정하지 않는다 — 클라이언트는 이 HTTP 400 을 gracefully 처리하거나 사용자에게 사전 안내해야 하며, 그 UX 설계는 본 자료 범위 밖이다.
KC-UNLINKGUARD-C2 guard 조건의 세 번째 예외인 isPasswordSet() 는 정확히 user.credentialManager().isConfiguredFor(PasswordCredentialModel.TYPE) 로 구현되어 있다 — 즉 password credential 이 configured 되어 있는지 여부로 판정한다 [L361-362] "private boolean isPasswordSet() {\n return user.credentialManager().isConfiguredFor(PasswordCredentialModel.TYPE);" official-vendor-doc password 존재 여부 판정 로직의 정확한 구현 다른 credential type(예: WebAuthn, OTP)이 이 guard 의 예외 조건에 포함되는지는 이 코드 조각만으로 알 수 없다 — 코드상 명시적으로 PasswordCredentialModel.TYPE 만 검사하며, count() > 1 / user.isFederated() 두 조건과의 OR 결합이 유일한 대안 경로다.
KC-UNLINKGUARD-C3 이 guard 를 위반할 때 사용자에게 노출되는 메시지는 federatedIdentityRemovingLastProviderMessage=You can not remove last federated identity as you do not have a password. 이다 [messages_en.properties L231] "federatedIdentityRemovingLastProviderMessage=You can not remove last federated identity as you do not have a password." official-vendor-doc 기본(en) 테마의 사용자향 오류 메시지 문구 이 메시지 텍스트가 커스텀 테마에서도 동일하게 노출된다는 보장은 아니다 — 테마 오버라이드 시 문구가 달라질 수 있다. 또한 이 메시지는 count() > 1 이나 user.isFederated() 조건으로 실패한 경우가 아니라 "password 없음"이 원인일 때만 정확히 들어맞는 문구다(메시지 키 이름 자체가 password 부재를 전제).

Strength 허용값 참조

official-vendor-doc — 본 문서 3개 claim 모두 Keycloak(Red Hat) 이 직접 소유·배포하는 공식 레포지토리의 엔진 소스 코드에서 나온 것이므로 이 등급을 사용. company-tech-blog 아님.

Usage Boundaries / 적용 경계

  • 이 자료가 직접 증명하는 것:
    • KC-UNLINKGUARD-C1: Keycloak Account REST resource 가 마지막 federated identity 제거를 서버 레벨에서 조건부 차단한다는 사실 (코드 존재 자체가 증거)
    • KC-UNLINKGUARD-C2: 그 조건 중 password 관련 예외의 정확한 구현 방식
    • KC-UNLINKGUARD-C3: 그 상황에서 사용자에게 노출되는 기본 오류 메시지 문구
  • 이 자료가 증명하지 않는 것:
    • 이 guard 가 모든 Keycloak 배포 릴리즈 버전(예: 특정 LTS 태그)에 동일하게 존재한다는 것 — main 브랜치 스냅샷일 뿐
    • Account Console 프론트엔드(웹 UI)가 이 400 응답을 어떻게 시각적으로 처리하는지 (버튼 비활성화, 에러 토스트 등) — 이는 프론트엔드 코드 별도 확인 필요
    • Admin API 나 Admin Console 을 통한 관리자 강제 unlink 에도 동일 guard 가 적용되는지 (이 파일은 Account REST resource, 즉 self-service 경로만 다룸)
  • 내 프로젝트에 적용하려면 추가 확인이 필요한 것:
    • 실제 사용 중인 Keycloak 배포 버전(릴리즈 태그)에서 이 guard 코드가 동일하게 존재하는지 재확인
    • 클라이언트(SPA/Account Console)가 이 HTTP 400 을 어떻게 처리할지의 UX 설계 — 본 자료는 서버 guard 존재만 증명하며 클라이언트 처리 방식은 별도 결정 사항

메모 / Notes

나중에 wiki로 옮길 때 참고할 짧은 메모. 검증되지 않은 내 추론은 여기에 두지 말 것.

  • 본 자료는 feature-keycloak-account-linking-sub-vs-email branch-note 의 D3 UNSUPPORTED_DECISION 라벨을 해소하는 근거로 사용 가능 — branch-note 의 Decision Evidence Map D3 행을 이 문서의 KC-UNLINKGUARD-C1 로 갱신 권장.
  • 인용 1 해석 후보 (미검증): 이 guard 의 존재는 "Keycloak 이 서버 레벨에서 잠금을 방지하니 프론트엔드에서 별도 안전장치가 필요 없다"는 결론까지는 뒷받침하지 않는다 — 400 에러를 사용자에게 사전 경고 없이 노출하는 것은 나쁜 UX 이므로, 클라이언트 측 사전 안내는 여전히 별도 설계 필요.
  • 추가로 봐야 할 동일 출처 페이지: Admin Console 쪽 identity provider 관리 코드(관리자가 강제로 unlink 시킬 때도 동일 guard 가 있는지) — 미확인.