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 |
|
|
|
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
- 원본 URL: https://github.com/keycloak/keycloak/blob/main/services/src/main/java/org/keycloak/services/resources/account/LinkedAccountsResource.java
- 실제 fetch 대상 (raw content):
- 아카이브 URL: (미제공)
- 저자 / 조직: Keycloak (Red Hat)
- 발행일: 지속 갱신되는
main브랜치 소스 (특정 릴리즈 태그 아님) - 마지막 확인일: 2026-07-15
왜 저장했는지 / 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.javaL315] "// Removing last social provider is not possible if you don't have other possibility to authenticate"
[
LinkedAccountsResource.javaL316] "if (!(session.users().getFederatedIdentitiesStream(realm, user).count() > 1 || user.isFederated() || isPasswordSet())) {"
[
LinkedAccountsResource.javaL317] "throw ErrorResponse.error(translateErrorMessage(Messages.FEDERATED_IDENTITY_REMOVING_LAST_PROVIDER), Response.Status.BAD_REQUEST);"
[
LinkedAccountsResource.javaL361-362] "private boolean isPasswordSet() { return user.credentialManager().isConfiguredFor(PasswordCredentialModel.TYPE);"
[
messages_en.propertiesL231] "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 경로만 다룸)
- 이 guard 가 모든 Keycloak 배포 릴리즈 버전(예: 특정 LTS 태그)에 동일하게 존재한다는 것 —
- 내 프로젝트에 적용하려면 추가 확인이 필요한 것:
- 실제 사용 중인 Keycloak 배포 버전(릴리즈 태그)에서 이 guard 코드가 동일하게 존재하는지 재확인
- 클라이언트(SPA/Account Console)가 이 HTTP 400 을 어떻게 처리할지의 UX 설계 — 본 자료는 서버 guard 존재만 증명하며 클라이언트 처리 방식은 별도 결정 사항
메모 / Notes
나중에 wiki로 옮길 때 참고할 짧은 메모. 검증되지 않은 내 추론은 여기에 두지 말 것.
- 본 자료는
feature-keycloak-account-linking-sub-vs-emailbranch-note 의 D3UNSUPPORTED_DECISION라벨을 해소하는 근거로 사용 가능 — branch-note 의 Decision Evidence Map D3 행을 이 문서의KC-UNLINKGUARD-C1로 갱신 권장. - 인용 1 해석 후보 (미검증): 이 guard 의 존재는 "Keycloak 이 서버 레벨에서 잠금을 방지하니 프론트엔드에서 별도 안전장치가 필요 없다"는 결론까지는 뒷받침하지 않는다 — 400 에러를 사용자에게 사전 경고 없이 노출하는 것은 나쁜 UX 이므로, 클라이언트 측 사전 안내는 여전히 별도 설계 필요.
- 추가로 봐야 할 동일 출처 페이지: Admin Console 쪽 identity provider 관리 코드(관리자가 강제로 unlink 시킬 때도 동일 guard 가 있는지) — 미확인.
Related / 관련
- raw/official-docs/keycloak-identity-brokering-overview-official — federated identity 모델 (본 guard 가 다루는
FederatedIdentityModel의 배경) - raw/official-docs/keycloak-first-login-flow — Confirm Link Existing Account flow 공식 (link 시점 정책, 본 문서는 unlink 시점 정책)
- raw/company-tech-blogs/keycloak-google-login-codemancers — takeover 시나리오 실무 사례 (본 문서는 그 대응책 중 하나인 lockout guard 의 엔진 증거)