들어가며 — "SSO 되나요?"라는 질문의 진짜 의미
B2B SaaS를 운영하다 보면 엔터프라이즈 영업 단계에서 반드시 받는 질문이 있습니다. "SSO 지원하나요?" 이때의 SSO는 "구글로 로그인" 같은 소셜 로그인이 아닙니다. 고객사가 묻는 것은 이것입니다.
"우리 회사 직원들이 **우리 회사의 IdP**(Okta, Entra ID, 자체 Keycloak 등)로 당신네 서비스에 로그인할 수 있나요?"
즉, 멀티테넌트 SaaS의 SSO란 **테넌트(고객사)마다 서로 다른 외부 IdP를 연동하는 페더레이션 아키텍처**를 뜻합니다. 단일 IdP를 붙이는 사내 SSO와는 완전히 다른 차원의 설계 문제입니다. 2026년 현재 엔터프라이즈 보안 체크리스트에서 SSO(그리고 SCIM)는 사실상 입찰 자격 요건이 되었고, passkeys 기본값화·Zero Trust 흐름 속에서 "고객사 IdP가 인증을 소유한다"는 원칙은 더욱 강해지고 있습니다.
이 글에서는 테넌트별 IdP 설정 모델(realm-per-tenant vs Keycloak Organizations), home realm discovery, 도메인 검증, JIT vs SCIM, 셀프서비스 온보딩 UI, 그리고 요금제의 SSO tax 논쟁까지 멀티테넌트 SSO의 전체 그림을 그려봅니다.
전체 아키텍처 조감도
먼저 등장인물을 정리합니다.
고객사 A (acme.com) 우리 SaaS 고객사 B (globex.io)
+--------------------+ +---------------------+ +--------------------+
| Okta (IdP) | | 중앙 인증 계층 | | Entra ID (IdP) |
| |<---->| (Keycloak 등) |<---->| |
| SAML 2.0 | SAML | | OIDC | OIDC |
+--------------------+ | - 테넌트별 IdP 설정 | +--------------------+
| - 도메인 -> IdP 라우팅|
고객사 C (스타트업) | - JIT / SCIM |
+--------------------+ | - 세션 관리 |
| IdP 없음 |----->| |
| 이메일+패스키 로그인 | +----------+----------+
+--------------------+ |
v
+---------------------+
| SaaS 애플리케이션 |
| (테넌트 격리된 인가) |
+---------------------+
핵심 설계 원칙은 세 가지입니다.
1. **애플리케이션은 IdP를 직접 모릅니다.** 앱은 중앙 인증 계층(자체 Keycloak, 또는 Auth0/WorkOS 같은 서비스)과만 OIDC로 통신하고, 고객사 IdP와의 SAML/OIDC 페더레이션은 인증 계층이 담당합니다. 이를 broker(중개자) 패턴이라 부릅니다.
2. **테넌트 식별이 인증보다 먼저입니다.** "누구인가"를 묻기 전에 "어느 회사 소속인가"를 알아야 올바른 IdP로 보낼 수 있습니다.
3. **인증은 위임해도 인가는 위임하지 않습니다.** 고객사 IdP는 "이 사람이 acme.com의 직원이다"까지만 보증하고, 우리 서비스 안에서의 역할과 권한은 우리가 관리합니다.
테넌트별 IdP 설정 모델 — realm-per-tenant vs Organizations
중앙 인증 계층을 Keycloak으로 구축한다고 할 때, 테넌트를 어떻게 모델링할지가 첫 번째 갈림길입니다.
모델 1: realm-per-tenant
테넌트마다 Keycloak realm을 하나씩 만드는 전통적인 방식입니다.
Keycloak
├── realm: acme (Okta SAML 페더레이션, acme 전용 클라이언트)
├── realm: globex (Entra OIDC 페더레이션, globex 전용 클라이언트)
└── realm: startupz (로컬 계정 + passkeys)
장점은 완벽한 격리입니다. 인증 플로우, 비밀번호 정책, 토큰 수명, 테마까지 테넌트별로 자유롭게 다르게 가져갈 수 있습니다. 그러나 치명적인 단점이 있습니다.
- **확장성**: realm은 무거운 객체입니다. 수백~수천 테넌트 규모에서 realm 수가 늘면 기동 시간, 캐시 메모리, 관리 콘솔 성능이 모두 악화됩니다. Keycloak 팀도 대량 realm 운영을 권장하지 않습니다.
- **운영 부담**: realm마다 클라이언트 등록, 키 회전, 설정 변경을 반복해야 합니다. 테넌트 100개면 설정 변경도 100번입니다(자동화 필수).
- **앱 통합 복잡성**: 앱이 테넌트마다 다른 issuer URL을 다뤄야 합니다. OIDC 라이브러리 다수가 멀티 issuer를 우아하게 지원하지 않습니다.
모델 2: 단일 realm + Keycloak Organizations
Keycloak 26에서 정식화된 [Organizations](https://www.keycloak.org/docs/latest/server_admin/index.html#_managing_organizations) 기능은 이 문제를 정면으로 해결합니다. 하나의 realm 안에 "organization"이라는 1급 테넌트 개념을 도입한 것입니다.
Keycloak
└── realm: saas-prod
├── organization: acme
│ ├── 도메인: acme.com, acme.co.kr (검증됨)
│ ├── identity provider: acme-okta (SAML)
│ └── 멤버: acme 소속 사용자들
├── organization: globex
│ ├── 도메인: globex.io
│ ├── identity provider: globex-entra (OIDC)
│ └── 멤버: globex 소속 사용자들
└── organization: startupz
├── 도메인: startupz.dev
└── (IdP 없음 — 로컬 인증)
Organizations가 제공하는 것들입니다.
- **organization별 IdP 연결**: 각 organization에 SAML/OIDC IdP를 연결하고, 해당 organization의 도메인 사용자를 그 IdP로 라우팅합니다.
- **도메인 매핑**: organization에 이메일 도메인을 등록하면, 로그인 화면에서 이메일만 보고 어느 organization인지 판별할 수 있습니다(home realm discovery의 빌트인 구현).
- **멤버십 관리**: 사용자 초대(invitation), organization 멤버 속성, organization 단위 멤버 조회 API를 제공합니다.
- **토큰에 organization 클레임**: 발급되는 토큰에 organization 정보가 클레임으로 포함되어, 앱이 테넌트 컨텍스트를 바로 알 수 있습니다.
{
"iss": "https://auth.example-saas.com/realms/saas-prod",
"sub": "f3a8c2e1-9b47-4d6a-8c21-0e5f7a9b3d44",
"preferred_username": "jane@acme.com",
"organization": {
"acme": {
"id": "b1e6f0c2-3d8a-47e5-9f12-6c4b8a0d2e91"
}
},
"exp": 1781234567,
"aud": "saas-web"
}
비교와 선택 기준
| 기준 | realm-per-tenant | 단일 realm + Organizations |
| --- | --- | --- |
| 격리 수준 | 최고 (정책/키/테마 전부 분리) | 논리적 격리 (정책은 realm 공유) |
| 테넌트 수 확장성 | 수십 개 수준에서 한계 | 수천 organization까지 현실적 |
| 앱 통합 | 테넌트별 issuer — 복잡 | 단일 issuer — 단순 |
| 테넌트별 인증 정책 차별화 | 자유 | 제한적 (인증 플로우는 realm 단위) |
| 운영 자동화 부담 | 큼 | 작음 |
| 적합한 경우 | 규제로 강한 격리가 필요한 소수 대형 고객 | 일반적인 B2B SaaS 대부분 |
2026년 시점의 결론은 명확합니다. **일반적인 B2B SaaS라면 단일 realm + Organizations가 기본값**이고, 금융·공공처럼 계약상 물리적 격리가 요구되는 예외 고객만 별도 realm(또는 별도 인스턴스)으로 분리하는 하이브리드가 현실적입니다. Keycloak 26.6의 zero-downtime rolling update 덕분에 단일 대형 realm 운영의 업그레이드 리스크도 과거보다 크게 줄었습니다.
Home Realm Discovery — 이메일 도메인 기반 IdP 라우팅
사용자가 로그인 페이지에 도착했을 때, 우리는 아직 그가 누구인지 모릅니다. 어느 IdP로 보낼지 결정하는 과정이 **home realm discovery(HRD)** 입니다. 대표적인 구현이 "이메일 먼저(identifier-first)" 패턴입니다.
1. 사용자가 이메일 입력: jane@acme.com
2. 시스템이 도메인 추출: acme.com
3. 도메인 -> organization -> IdP 매핑 조회
- acme.com은 organization acme, IdP는 acme-okta(SAML)
4. 분기:
- IdP 있음 -> acme-okta로 SAML AuthnRequest 리다이렉트
- IdP 없음 -> 비밀번호/패스키 입력 UI 표시
5. IdP 인증 완료 -> 콜백 -> 세션 발급
Keycloak Organizations를 쓰면 이 플로우가 기본 제공됩니다. 로그인 페이지에서 이메일을 입력하면 도메인이 organization에 매핑되어 있는지 확인하고, 연결된 IdP로 자동 리다이렉트합니다. 직접 구현한다면 다음과 같은 형태가 됩니다.
@PostMapping("/auth/discover")
public ResponseEntity<DiscoveryResponse> discover(@RequestBody DiscoveryRequest req) {
String domain = extractDomain(req.email()); // "acme.com"
Optional<TenantIdpConfig> idp = tenantService.findVerifiedIdpByDomain(domain);
if (idp.isPresent()) {
// IdP 연동 테넌트: 인가 요청 URL 생성 (OIDC면 authorize, SAML이면 SSO URL)
String redirect = authUrlBuilder.build(idp.get(), req.relayState());
return ResponseEntity.ok(DiscoveryResponse.federated(redirect));
}
// 비연동 테넌트: 로컬 인증(비밀번호/패스키) 진행
return ResponseEntity.ok(DiscoveryResponse.local());
}
HRD 설계 시 주의점입니다.
- **테넌트 존재 여부를 노출하지 말 것**: "이 도메인은 등록되지 않았습니다" 같은 응답은 고객사 목록을 추측하게 합니다. IdP 유무와 무관하게 동일한 다음 단계 UI를 보여주는 것이 안전합니다.
- **개인 도메인 처리**: gmail.com 같은 공용 도메인은 organization 매핑 대상에서 제외해야 합니다.
- **복수 IdP 도메인**: 인수합병 등으로 한 도메인에 복수 IdP가 걸릴 수 있습니다. 이 경우 IdP 선택 화면을 보여주는 폴백이 필요합니다.
SP-initiated 흐름에서 테넌트 식별
HRD 외에도 테넌트를 식별하는 통로는 여러 가지입니다.
| 방법 | 예 | 장단점 |
| --- | --- | --- |
| 이메일 도메인 (identifier-first) | jane@acme.com 입력 | 범용적, UX 한 단계 추가 |
| 테넌트 전용 URL | acme.example-saas.com | 북마크 친화적, 서브도메인 관리 필요 |
| 테넌트 전용 로그인 경로 | /login/acme | 구현 단순, URL 공유 시 테넌트 노출 |
| IdP-initiated SSO | 고객사 포털에서 앱 타일 클릭 | 고객 친화적이나 보안상 권장도 낮음 (특히 SAML unsolicited response) |
실무에서는 "서브도메인 + identifier-first 폴백" 조합이 가장 흔합니다. IdP-initiated SAML은 CSRF성 공격 표면 때문에 가능하면 SP-initiated로 강제하고, 고객사 포털 타일은 SP의 로그인 URL을 가리키게 안내하는 것이 좋습니다.
도메인 검증 — 페더레이션의 신뢰 기반
도메인과 organization의 매핑은 보안의 근간입니다. 검증 없이 아무 도메인이나 등록할 수 있다면, 악의적 테넌트가 acme.com을 자기 organization에 등록하고 acme 직원의 로그인을 자기 IdP로 가로챌 수 있습니다.
표준적인 검증 방식은 DNS TXT 레코드입니다.
1. 테넌트 관리자가 도메인 등록 요청: acme.com
2. 시스템이 검증 토큰 발급:
saas-verify=4f8a2c1e-7b3d-4e9a-b6f0-1c5d8e2a9b47
3. 관리자가 acme.com의 DNS에 TXT 레코드 추가
4. 시스템이 DNS 조회로 토큰 확인 -> 검증 완료
5. 이후 주기적 재검증 (도메인 소유권 상실 감지)
검증 측 구현의 핵심은 단순합니다
dig +short TXT acme.com | grep "saas-verify=4f8a2c1e-7b3d-4e9a-b6f0-1c5d8e2a9b47"
추가 원칙입니다.
- **검증 전에는 라우팅 활성화 금지**: 검증 완료 전까지 해당 도메인의 HRD 라우팅을 켜면 안 됩니다.
- **도메인 충돌 처리**: 이미 다른 organization이 검증한 도메인은 등록을 거부하고, 분쟁 절차(수동 심사)를 둡니다.
- **재검증 주기**: 도메인 만료/매각을 감지하기 위해 주기적으로 재확인합니다.
JIT Provisioning vs SCIM
고객사 IdP로 인증은 됐는데, 우리 서비스 안의 사용자 레코드는 누가 만들까요?
JIT (Just-in-Time) Provisioning
첫 로그인 시점에 SAML assertion/OIDC 클레임을 보고 사용자를 즉석 생성하는 방식입니다.
public User jitProvision(OidcUserInfo info, Organization org) {
return userRepository.findByIssuerAndSubject(info.issuer(), info.subject())
.orElseGet(() -> {
User user = User.builder()
.email(info.email())
.displayName(info.name())
.organizationId(org.id())
.role(org.defaultRole()) // 최소 권한이 기본
.source(UserSource.FEDERATED_JIT)
.build();
auditLog.record("user.jit_provisioned", user);
return userRepository.save(user);
});
}
비교
| 기준 | JIT | SCIM |
| --- | --- | --- |
| 계정 생성 시점 | 첫 로그인 때 | 입사/배정 즉시 (선행 생성) |
| 계정 비활성화 | 불가 — 로그인 안 하는 사용자는 모름 | IdP가 즉시 푸시 |
| 그룹/권한 동기화 | 로그인 시점의 클레임 스냅숏 | 변경 시 실시간 반영 |
| 구현 비용 | 낮음 | SCIM 서버 구현 필요 |
| 로그인 전 협업 기능 (멤버 초대, 멘션) | 사용자가 없어서 불완전 | 전체 명부 선반영 |
결론은 "둘 다"입니다. **JIT는 베이스라인, SCIM은 엔터프라이즈 티어의 업그레이드**로 두는 것이 업계 표준 패턴입니다. 중요한 것은 deprovisioning 관점입니다. JIT만 있는 테넌트는 퇴사자 계정이 우리 쪽에 살아남는다는 사실을 고객 보안팀에 명확히 알리고, 세션 수명 단축과 재인증 주기로 보완해야 합니다.
셀프서비스 SSO 온보딩 UI 설계
엔터프라이즈 고객이 수십 곳이 되면 SSO 설정을 지원 티켓으로 처리하는 방식은 파산합니다. 테넌트 관리자가 직접 설정하는 셀프서비스 위저드가 답입니다.
[1단계] 프로토콜 선택
SAML 2.0 | OIDC
|
[2단계] 우리 쪽 정보 제공 (복사 가능한 형태로)
SAML: SP Entity ID, ACS URL, SP 메타데이터 XML 다운로드
OIDC: Redirect URI, 권장 스코프
|
[3단계] 고객 IdP 정보 입력
SAML: IdP 메타데이터 URL 또는 XML 업로드 (서명 인증서 포함)
OIDC: issuer URL (discovery 자동 조회), client_id, client_secret
|
[4단계] 속성 매핑
email, 이름, 그룹 클레임 매핑 (드롭다운 + 기본값 제공)
|
[5단계] 테스트 로그인
설정 활성화 전, 관리자 본인이 테스트 인증 수행
실패 시 SAML 응답/에러를 사람이 읽을 수 있게 표시
|
[6단계] 활성화 정책 선택
- SSO 선택적 (로컬 로그인 병행)
- SSO 강제 (로컬 로그인 차단, 단 break-glass 관리자 예외)
설계 디테일이 성패를 가릅니다.
- **메타데이터 자동 파싱**: SAML 메타데이터 XML이나 OIDC discovery 문서를 업로드/입력하면 엔드포인트와 인증서를 자동 추출합니다. 수동 입력 항목이 늘수록 오설정 티켓이 늘어납니다.
- **테스트 로그인 필수화**: 테스트 통과 전 활성화를 막는 것만으로 "SSO 켰더니 전사 로그인 불가" 사고를 예방할 수 있습니다.
- **break-glass 계정**: SSO 강제 모드에서도 테넌트 관리자 1인은 로컬 인증(강한 MFA 필수)으로 들어올 수 있어야 합니다. IdP 장애나 인증서 만료 시 잠금 해제 경로가 없으면 서포트 지옥이 열립니다.
- **인증서 만료 모니터링**: SAML 서명 인증서 만료 30일/7일 전에 테넌트 관리자에게 자동 알림을 보냅니다. SAML 연동 장애 원인 1위가 인증서 만료입니다.
Keycloak 기반이라면 이 위저드의 백엔드는 Admin API 호출로 구현합니다.
organization에 OIDC IdP를 연결하는 예 (Admin API)
curl -X POST "https://auth.example-saas.com/admin/realms/saas-prod/identity-provider/instances" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"alias": "globex-entra",
"providerId": "oidc",
"config": {
"issuer": "https://login.microsoftonline.com/TENANT-GUID/v2.0",
"clientId": "globex-client-id",
"clientSecret": "globex-client-secret",
"useJwksUrl": "true",
"validateSignature": "true"
}
}'
IdP를 organization에 링크
curl -X POST "https://auth.example-saas.com/admin/realms/saas-prod/organizations/ORG-ID/identity-providers" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '"globex-entra"'
요금제와 SSO Tax 논쟁
SSO를 어느 요금제에 넣을 것인가는 기술 문제이자 비즈니스 논쟁입니다. SSO를 최상위 엔터프라이즈 티어에만 넣고 큰 가격 점프를 만드는 관행을 비판하는 용어가 **SSO tax**입니다. [sso.tax](https://sso.tax) 사이트가 이 관행을 공개적으로 추적하면서 업계 논쟁이 됐습니다.
- **비판 측**: SSO는 보안 기능이며, 보안을 프리미엄에 가두는 것은 전체 생태계의 보안 수준을 떨어뜨린다. 특히 SSO 도입 의지가 있는 중소 고객이 가격 때문에 포기하는 것은 사회적 손실이다.
- **옹호 측**: 엔터프라이즈 SSO는 연동·지원 비용이 실제로 크고, 지불 의사가 높은 고객을 구분하는 자연스러운 가격 차별 지점이다.
2026년의 현실적인 절충안은 이렇게 정리됩니다.
| 기능 | 권장 배치 |
| --- | --- |
| 소셜 로그인, passkeys | 전 티어 |
| SAML/OIDC SSO (단일 IdP) | 중간 티어부터, 또는 저가 애드온 |
| SCIM 프로비저닝 | 엔터프라이즈 티어 |
| 감사 로그 API, 세션 정책 커스터마이즈 | 엔터프라이즈 티어 |
보안 커뮤니티의 압력으로 "SSO 자체는 낮은 티어로, 거버넌스 기능(SCIM, 감사, 정책)은 상위 티어로"라는 패턴이 점차 표준이 되고 있습니다. 아키텍처 관점의 시사점은 분명합니다. **SSO를 나중에 특정 티어에 붙였다 뗐다 할 수 있도록, 인증 계층을 처음부터 테넌트별 기능 플래그로 설계해 두어야 한다**는 것입니다.
세션과 권한의 테넌트 격리
세션 격리
한 사용자가 두 테넌트에 속할 수 있습니다(예: 컨설턴트가 고객사 두 곳의 워크스페이스에 모두 멤버). 이때 세션과 토큰의 테넌트 컨텍스트를 명확히 해야 합니다.
- **토큰에 테넌트 클레임을 박습니다**: 위에서 본 organization 클레임처럼, 모든 액세스 토큰에는 "이 토큰이 어느 테넌트 컨텍스트인가"가 포함되어야 합니다.
- **테넌트 전환은 재발급으로**: 워크스페이스 전환 시 기존 토큰을 재사용하지 않고 해당 테넌트 컨텍스트의 토큰을 새로 발급합니다.
- **테넌트별 세션 정책**: 엔터프라이즈 테넌트는 "유휴 30분, 절대 8시간, IdP 세션과 수명 연동" 같은 자체 정책을 요구합니다. 세션 정책을 테넌트 설정으로 외부화해 두어야 합니다.
인가 격리
모든 API 요청은 "토큰의 테넌트 == 리소스의 테넌트"를 강제하는 단일 미들웨어를 통과해야 합니다.
@Component
public class TenantIsolationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest req,
HttpServletResponse res,
FilterChain chain) throws IOException, ServletException {
String tokenTenant = jwtContext.organizationId(); // 토큰 클레임에서
String resourceTenant = tenantResolver.fromRequest(req); // URL/리소스에서
if (!tokenTenant.equals(resourceTenant)) {
auditLog.record("authz.cross_tenant_denied", tokenTenant, resourceTenant);
res.sendError(HttpServletResponse.SC_NOT_FOUND); // 403 대신 404로 존재 은닉
return;
}
chain.doFilter(req, res);
}
}
cross-tenant 접근 시도는 보안 이벤트로 기록하고, 응답은 404로 리소스 존재 자체를 숨기는 것이 관례입니다. 더 세밀한 권한(문서 단위 공유 등)이 필요해지면 ReBAC 모델([OpenFGA](https://openfga.dev/docs), [Google Zanzibar](https://research.google/pubs/pub48190/) 계열)로의 확장을 고려합니다. 이때도 테넌트 경계는 관계 그래프의 최상위 차원으로 유지해야 합니다.
감사와 컴플라이언스
엔터프라이즈 고객의 보안팀이 요구하는 것들입니다.
- **인증 이벤트 감사 로그**: 로그인 성공/실패, SSO 설정 변경, 도메인 검증, 관리자 권한 변경을 모두 기록하고, 테넌트 관리자가 자기 테넌트 분을 조회/내보내기 할 수 있어야 합니다(SIEM 연동용 API 포함).
- **SOC 2 / ISO 27001 증적**: SSO 설정 변경 이력과 접근 통제 기록은 우리 쪽 감사이기도 합니다. 변경한 주체가 테넌트 관리자인지 우리 운영자인지 구분되어야 합니다.
- **데이터 주재(residency)**: 인증 로그에도 리전 요구가 붙는 경우가 있습니다. 인증 계층의 리전 분리 가능성을 아키텍처 단계에서 검토해 둡니다.
- **세션 증명**: "퇴사자 D의 마지막 접근이 언제였는가"에 답할 수 있어야 합니다. 세션 생성/만료/폐기 이벤트를 보존 정책과 함께 저장합니다.
실전 시나리오 모음
실제 운영에서 만나는 시나리오로 설계를 검증해 봅시다.
**시나리오 1 — 고객사가 Okta에서 Entra로 이전**
acme가 IdP를 교체합니다. 사용자 매칭 키가 IdP의 subject였다면 전원이 신규 사용자로 중복 생성됩니다. 교훈: 페더레이션 사용자의 매칭 키는 "issuer+subject"를 1순위로 하되, 검증된 도메인의 이메일을 2순위 링크 키로 두고, IdP 교체 마이그레이션 절차(관리자가 구 IdP 링크를 일괄 재매핑)를 제품 기능으로 준비해야 합니다.
**시나리오 2 — 인수합병으로 도메인 통합**
globex가 initech를 인수해 initech.com 사용자들이 globex organization으로 합류합니다. 도메인 재검증, 기존 initech organization과의 멤버 병합, 진행 중 세션 처리(강제 재인증)가 필요합니다. organization 간 멤버 이동 API와 감사 로그가 없으면 수작업 지옥이 됩니다.
**시나리오 3 — IdP 장애 시 로그인 불가**
acme의 Okta가 장애를 일으키면 acme 사용자 전원이 로그인 불가가 됩니다. 이것은 의도된 동작이지만(인증 소유권이 고객에게 있으므로), break-glass 관리자 계정과 상태 페이지 안내, 그리고 "IdP 장애는 우리 장애가 아님"을 보여줄 수 있는 연동 상태 모니터링이 준비되어 있어야 합니다.
**시나리오 4 — SSO 강제 후 API 토큰**
acme가 SSO 강제를 켰는데, acme 직원이 과거 발급한 개인 API 토큰은 여전히 동작합니다. SSO 강제 정책에 "기존 토큰 무효화 + IdP 세션 기반 재발급" 옵션을 포함할지 결정해야 합니다. 엔터프라이즈 보안팀은 대부분 무효화를 기대합니다.
마치며
멀티테넌트 SSO는 "SAML 라이브러리 붙이기"가 아니라 테넌트 모델링, 신뢰 검증, 수명주기, 과금까지 얽힌 제품 아키텍처 문제입니다. 핵심을 요약합니다.
1. B2B SaaS의 SSO는 테넌트별 외부 IdP 페더레이션입니다. 앱과 IdP 사이에 broker 계층을 두고, 앱은 단일 issuer만 보게 하십시오.
2. 테넌트 모델은 단일 realm + Keycloak Organizations(26+)가 기본값, 강한 격리가 필요한 예외만 분리하십시오.
3. 이메일 도메인 기반 HRD와 DNS 도메인 검증이 라우팅 신뢰의 토대입니다. 검증 없는 도메인 매핑은 계정 탈취 통로가 됩니다.
4. JIT는 시작점, SCIM은 엔터프라이즈 완성형입니다. deprovisioning 갭을 고객에게 정직하게 설명하십시오.
5. 셀프서비스 온보딩 위저드(메타데이터 자동 파싱, 테스트 로그인, break-glass)가 SSO 지원 비용을 결정합니다.
6. SSO tax 논쟁을 의식해, 인증 기능을 테넌트별 플래그로 설계해 가격 정책 변화에 대비하십시오.
다음 글에서는 이 아키텍처의 마지막 난제인 Single Logout — 로그인보다 어려운 로그아웃 설계를 다룹니다.
참고 자료
- [Keycloak Server Administration — Managing Organizations](https://www.keycloak.org/docs/latest/server_admin/index.html#_managing_organizations)
- [Keycloak Documentation](https://www.keycloak.org/documentation)
- [Keycloak Release Notes](https://www.keycloak.org/docs/latest/release_notes/index.html)
- [OpenID Connect Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html)
- [SAML 2.0 Core Specification (OASIS)](https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf)
- [RFC 6749 — The OAuth 2.0 Authorization Framework](https://datatracker.ietf.org/doc/html/rfc6749)
- [RFC 9700 — Best Current Practice for OAuth 2.0 Security](https://datatracker.ietf.org/doc/html/rfc9700)
- [RFC 7644 — SCIM: Protocol](https://datatracker.ietf.org/doc/html/rfc7644)
- [OAuth 2.1 Draft](https://datatracker.ietf.org/doc/draft-ietf-oauth-v2-1/)
- [The SSO Wall of Shame (sso.tax)](https://sso.tax)
- [OpenFGA Documentation](https://openfga.dev/docs)
- [Google Zanzibar 논문](https://research.google/pubs/pub48190/)
- [FIDO Alliance — Passkeys](https://fidoalliance.org/passkeys/)
현재 단락 (1/261)
B2B SaaS를 운영하다 보면 엔터프라이즈 영업 단계에서 반드시 받는 질문이 있습니다. "SSO 지원하나요?" 이때의 SSO는 "구글로 로그인" 같은 소셜 로그인이 아닙니다. 고...