들어가며
사내 시스템이 10개를 넘어가는 순간, 그리고 SaaS 도입이 본격화되는 순간, 모든 조직은 같은 질문에 부딪힙니다. "사용자가 비밀번호를 도대체 몇 개나 기억해야 하는가?" Single Sign-On(SSO)은 이 질문에 대한 산업 표준의 답이고, 그 답을 구현하는 프로토콜이 바로 SAML 2.0, OAuth 2.0, 그리고 OpenID Connect(OIDC)입니다.
2026년 현재 이 주제를 다시 정리해야 하는 이유는 분명합니다.
1. **OAuth 2.1 드래프트의 사실상 표준화** — 아직 IETF 드래프트(draft-ietf-oauth-v2-1) 상태이지만, PKCE 의무화와 Implicit/ROPC 제거는 이미 업계 베스트 프랙티스로 굳어졌습니다. RFC 6749, RFC 7636, RFC 9700(OAuth 2.0 Security BCP)을 하나로 통합하는 작업입니다.
2. **passkeys의 기본값화** — WebAuthn/FIDO2 기반 passkeys가 1차 인증 수단으로 자리 잡으면서, "SSO 프로토콜이 인증 결과를 어떻게 전달하는가"와 "사용자가 IdP에서 어떻게 인증하는가"를 분리해서 이해하는 것이 더 중요해졌습니다. Keycloak 26.6은 로그인 폼에 passkeys를 conditional/modal UI로 통합했습니다.
3. **AI 에이전트 아이덴티티** — 사람이 아닌 주체(non-human identity)가 폭증하면서, 위임(delegation)을 다루는 OAuth 계열 프로토콜의 중요성이 커졌습니다. Keycloak 26.6의 OAuth Client ID Metadata Document(CIMD) 실험 지원은 MCP(Model Context Protocol) authorization server 시나리오를 겨냥한 것입니다.
이 글에서는 SSO의 동작 원리부터 세 프로토콜의 역사와 역할 차이, 그리고 "무엇을 언제 쓰는가"에 대한 의사결정 기준까지 한 번에 정리합니다.
SSO의 동작 원리 — 신뢰를 중앙으로 모으기
SSO의 본질은 한 문장으로 요약됩니다. **인증(authentication)을 각 애플리케이션에서 떼어내 중앙의 신뢰 기관에 위임하고, 애플리케이션은 그 기관이 발급한 "증명서"만 검증한다.**
핵심 등장인물: IdP, SP, RP
| 용어 | 풀네임 | 진영 | 역할 |
| --- | --- | --- | --- |
| IdP | Identity Provider | SAML/공통 | 사용자를 실제로 인증하고 증명(assertion/token)을 발급 |
| SP | Service Provider | SAML | IdP의 증명을 믿고 서비스를 제공하는 애플리케이션 |
| RP | Relying Party | OIDC | OIDC에서 SP에 해당하는 용어. IdP(OP)에 의존하는 쪽 |
| OP | OpenID Provider | OIDC | OIDC에서 IdP에 해당하는 용어 |
| AS | Authorization Server | OAuth | 액세스 토큰을 발급하는 인가 서버 |
| RS | Resource Server | OAuth | 액세스 토큰을 검증하고 API를 제공하는 서버 |
용어는 진영마다 다르지만 구조는 같습니다. "인증하는 쪽(IdP/OP/AS)"과 "믿는 쪽(SP/RP/RS)"이 사전에 신뢰 관계(trust)를 맺고, 사용자는 인증하는 쪽에서 한 번만 로그인하면 됩니다.
SSO의 일반화된 흐름
[사용자 브라우저] [SP / RP 애플리케이션] [IdP / OP]
| | |
|--- 1. 앱 접근 ------------>| |
| | |
|<-- 2. "인증 필요, IdP로 가라" (리다이렉트) --| |
| | |
|--- 3. 인증 요청 (AuthnRequest / authorize) --------->|
| | |
|<-- 4. 로그인 UI (비밀번호, passkey, MFA...) ---------|
|--- 5. 자격 증명 제출 ------------------------------->|
| | |
|<-- 6. 증명서 발급 + 앱으로 리다이렉트 ---------------|
| (SAML Response / authorization code) |
| | |
|--- 7. 증명서 전달 -------->| |
| |-- 8. 검증 (서명/코드교환)|
|<-- 9. 앱 세션 수립, 서비스 제공 --| |
| | |
이후 다른 앱 접근 시: IdP에 이미 SSO 세션이 있으므로
4~5단계(로그인 UI)가 생략되고 즉시 6단계로 진행 → 이것이 SSO
핵심은 마지막 줄입니다. **IdP가 자신의 도메인에 SSO 세션(보통 쿠키)을 유지하고 있기 때문에**, 두 번째 앱부터는 로그인 화면 없이 증명서가 즉시 발급됩니다. 사용자 입장에서는 "한 번 로그인했더니 모든 앱이 열리는" 경험이 됩니다.
신뢰는 어떻게 성립하는가
SSO가 성립하려면 SP와 IdP가 사전에 다음을 교환해야 합니다.
- **SAML**: 메타데이터 XML 교환 — entityID, 서명 검증용 X.509 인증서, 엔드포인트 URL
- **OIDC**: 클라이언트 등록 — client_id, client_secret(또는 비대칭 키), redirect URI 화이트리스트. IdP 쪽 정보는 Discovery 문서로 자동 발견
이 신뢰 설정이 깨져 있으면(인증서 만료, redirect URI 불일치 등) SSO는 동작하지 않습니다. 운영에서 만나는 장애의 대부분이 여기서 발생합니다.
세션 vs 토큰 — 상태를 어디에 둘 것인가
SSO를 이해하려면 먼저 "로그인 상태"를 유지하는 두 가지 방식을 구분해야 합니다.
세션 기반 (stateful)
[브라우저] --(Cookie: JSESSIONID=abc123)--> [서버]
|
v
[세션 저장소 (메모리/Redis)]
abc123 -> userId=42, roles=[admin]
- 서버가 세션 저장소에 상태를 보관하고, 브라우저에는 불투명한 세션 ID만 쿠키로 전달합니다.
- 장점: 서버가 즉시 세션을 무효화할 수 있음(강제 로그아웃), 쿠키에 민감 정보 없음.
- 단점: 수평 확장 시 세션 저장소 공유 필요, 도메인 경계를 넘기 어려움(쿠키는 도메인에 종속).
토큰 기반 (stateless)
[클라이언트] --(Authorization: Bearer eyJhbGciOi...)--> [API 서버]
|
v
서명 검증만으로 자체 판단
(저장소 조회 불필요)
- 서명된 자기완결(self-contained) 토큰(주로 JWT)에 사용자 정보와 권한을 담아 클라이언트가 들고 다닙니다.
- 장점: 서버 무상태(stateless), 도메인/서비스 경계를 자유롭게 넘음, 마이크로서비스에 적합.
- 단점: 발급된 토큰은 만료 전까지 즉시 무효화가 어려움 → 짧은 수명 + refresh token 조합으로 보완.
SSO에서는 둘 다 쓴다
흔한 오해가 "토큰 기반이 세션 기반을 대체했다"는 것인데, 실제 SSO 아키텍처에서는 **둘이 공존**합니다.
| 위치 | 상태 유지 방식 | 역할 |
| --- | --- | --- |
| IdP 도메인 | 세션(쿠키) | SSO 세션 — 재로그인 생략의 근거 |
| 웹 앱(서버 렌더링) | 앱 자체 세션(쿠키) | IdP 증명 검증 후 수립하는 로컬 세션 |
| SPA/모바일 → API | 토큰(Bearer) | API 호출 인가 |
IdP의 SSO 세션은 쿠키이고, 앱이 받아오는 것은 토큰이며, 전통적 웹 앱은 토큰 검증 후 다시 자체 세션을 만듭니다. 이 세 가지 수명을 구분해서 관리하는 것이 SSO 운영의 핵심입니다(로그아웃이 어려운 이유이기도 합니다).
세 프로토콜의 역사 — 왜 셋이나 존재하는가
2002 2005 2006~2010 2012 2014 2025~
| | | | | |
SAML 1.0 SAML 2.0 OpenID 1/2 OAuth 2.0 OIDC 1.0 OAuth 2.1
(OASIS) (OASIS, OAuth 1.0a (RFC 6749) (OAuth 2.0 (draft,
엔터프라이즈 (웹 2.0 API (인가 프레임 위에 인증 PKCE 의무화,
SSO 표준화) 위임 문제) 워크) 레이어 추가) Implicit 제거)
SAML 2.0 (2005) — 엔터프라이즈 웹 SSO의 원조
SAML(Security Assertion Markup Language)은 OASIS가 표준화한 XML 기반 프로토콜입니다. 시대 배경이 중요합니다. 2005년에는 스마트폰도 SPA도 없었고, 기업의 관심사는 "사내 직원이 여러 웹 애플리케이션에 한 번만 로그인하게 하는 것"이었습니다. 그래서 SAML은 브라우저 리다이렉트와 HTML 폼 POST를 전제로 설계되었고, XML Signature로 무결성을 보장합니다. 지금도 Workday, Salesforce, AWS IAM Identity Center 등 엔터프라이즈 SaaS의 B2B SSO 연동에서 가장 널리 쓰입니다.
OAuth 2.0 (2012) — 인가(Authorization) 프레임워크
OAuth는 전혀 다른 문제에서 출발했습니다. "내 Google 연락처를 제3자 앱이 읽게 해주고 싶은데, Google 비밀번호를 그 앱에 알려주고 싶지는 않다." 즉 **비밀번호 공유 없는 권한 위임(delegated authorization)**이 목적입니다. RFC 6749로 표준화된 OAuth 2.0은 access token이라는 제한된 권한의 열쇠를 발급하는 프레임워크이며, **사용자가 누구인지 알려주는 표준 방법은 정의하지 않습니다.** 이것이 가장 많이 오해되는 지점입니다.
OIDC (2014) — OAuth 2.0 위의 인증 레이어
"OAuth로 로그인을 구현하자"는 수요가 폭발하자, 각 회사가 제각각의 비표준 방식(Facebook Connect 등)을 만들었습니다. OpenID Connect 1.0은 이 혼란을 정리한 표준으로, OAuth 2.0 흐름 위에 **ID Token(JWT)** 이라는 인증 결과물과 UserInfo 엔드포인트, Discovery, 동적 클라이언트 등록을 얹었습니다. 한 줄 요약: **OIDC = OAuth 2.0 + 표준화된 인증 + JWT + 메타데이터 자동 발견.**
OAuth 2.1 (진행 중) — 14년치 보안 교훈의 통합
OAuth 2.1은 새 기능 추가가 아니라 **정리(consolidation)** 입니다. RFC 6749 + RFC 7636(PKCE) + RFC 9700(Security BCP)의 내용을 합치고, 위험한 패턴을 제거합니다.
- 모든 클라이언트에 PKCE 의무화 (confidential client 포함)
- Implicit Grant(response_type=token) 삭제
- Resource Owner Password Credentials(ROPC) Grant 삭제
- Bearer 토큰의 URL 쿼리 스트링 전달 금지
- Refresh token은 sender-constrained 또는 회전(rotation) 의무화
2026년에 새로 설계하는 시스템이라면 "OAuth 2.0을 쓰되 2.1의 제약을 그대로 따른다"가 정답입니다.
인증 vs 인가 — 가장 중요한 구분
| 질문 | 개념 | 담당 프로토콜 |
| --- | --- | --- |
| 너는 누구인가? | 인증 (Authentication, AuthN) | SAML, OIDC |
| 너는 무엇을 할 수 있는가? | 인가 (Authorization, AuthZ) | OAuth 2.0 |
비유하자면 이렇습니다.
- **OIDC/SAML** = 신분증 발급. "이 사람은 김영주이고, 우리 IdP가 5분 전에 passkey로 인증했음을 보증한다."
- **OAuth 2.0** = 호텔 카드키 발급. "이 카드는 305호와 헬스장 문을 열 수 있다. 소지자가 누구인지는 카드가 말해주지 않는다."
여기서 악명 높은 안티패턴이 나옵니다. **access token을 인증 증거로 쓰는 것**입니다. access token은 "API를 호출할 권한"의 증명일 뿐, "지금 이 요청을 보낸 사람이 토큰의 주인"이라는 보장이 없습니다. 다른 앱에 발급된 access token을 탈취해 로그인에 재사용하는 공격이 실제로 있었고, OIDC의 ID Token(aud 클레임으로 수신자를 못 박은 JWT)이 바로 이 문제의 해법입니다. 로그인 판단은 반드시 ID Token으로 해야 합니다.
세 프로토콜의 실제 흐름 비교
SAML 2.0 — SP-initiated Web SSO
[브라우저] [SP: app.example.com] [IdP: idp.corp.com]
| | |
|--- GET /dashboard -------->| |
|<-- 302 Redirect ----------- (SAMLRequest=base64+deflate)|
| | |
|--- GET /sso?SAMLRequest=...&RelayState=... ------------>|
|<-- 로그인 화면 (이미 SSO 세션 있으면 생략) -------------|
|--- 자격 증명 제출 -------------------------------------->|
| | |
|<-- 200 + 자동 제출 HTML 폼 (SAMLResponse=base64) -------|
|--- POST /acs (SAMLResponse, RelayState) -->| |
| |-- XML 서명 검증, Assertion |
| | 파싱, 조건(시간/수신자) |
|<-- Set-Cookie: 앱 세션 ----| 확인 |
특징: 모든 것이 브라우저를 경유하는 front-channel 중심(POST 바인딩), 결과물은 XML Assertion, SP와 IdP 간 직접 통신이 없어도 동작 가능.
OIDC — Authorization Code Flow + PKCE
[브라우저/앱] [RP: app.example.com] [OP: idp.corp.com]
| | |
|--- GET /login ------------>| |
| |-- code_verifier 생성, |
| | code_challenge 계산 |
|<-- 302 /authorize?response_type=code&client_id=... |
| &scope=openid+profile&state=...&nonce=... |
| &code_challenge=...&code_challenge_method=S256 |
| | |
|--- GET /authorize --------------------------------------->
|<-- 로그인 화면 (SSO 세션 있으면 생략) -------------------|
|--- 인증 완료 --------------------------------------------->
|<-- 302 redirect_uri?code=AUTH_CODE&state=... ------------|
|--- GET /callback?code=... ->| |
| |--- POST /token ----------->|
| | (code + code_verifier |
| | + client 인증) |
| |<-- id_token, access_token, |
| | refresh_token ----------|
| |-- id_token 서명/클레임 검증 |
|<-- 로그인 완료 ------------| |
특징: 토큰은 back-channel(서버 간 TLS 직통)로 전달, 결과물은 JWT, PKCE로 코드 탈취 방어, Discovery로 설정 자동화.
OAuth 2.0 단독 — 제3자 API 위임
OIDC와 흐름은 같지만 scope에 openid가 없고 id_token이 발급되지 않습니다. 결과물은 access token뿐이며, 용도는 "사용자 식별"이 아니라 "리소스 서버 API 호출"입니다.
비교 테이블 — 한눈에 보기
| 항목 | SAML 2.0 | OAuth 2.0 | OIDC 1.0 |
| --- | --- | --- | --- |
| 표준화 | OASIS, 2005 | IETF RFC 6749, 2012 | OpenID Foundation, 2014 |
| 본질 | 인증 + 속성 전달 | 인가 위임 프레임워크 | OAuth 2.0 위의 인증 레이어 |
| 데이터 포맷 | XML (Assertion) | 정의 안 함 (보통 JWT/opaque) | JWT (ID Token) + JSON |
| 무결성 보장 | XML Signature | TLS + 토큰 서명(구현 의존) | JWS 서명 (JWKS로 키 배포) |
| 전달 채널 | 주로 front-channel (Redirect/POST) | back-channel 중심 | back-channel 중심 |
| 메타데이터 | 메타데이터 XML 수동 교환 | 없음 (RFC 8414로 보완) | Discovery 문서 자동 발견 |
| 모바일/SPA 적합성 | 낮음 | 높음 (PKCE) | 높음 (PKCE) |
| API 인가 | 부적합 | 본업 | access token 부분은 OAuth 그대로 |
| 로그아웃 표준 | Single Logout (SLO) | 없음 | RP-Initiated/Back-Channel Logout |
| 주 사용처 | 엔터프라이즈 B2B SSO | API 권한 위임, 서비스 간 호출 | 소비자/직원 로그인, 모던 앱 전반 |
프로토콜 선택 의사결정 트리
시작
|
+-- Q1. 사용자 로그인(인증)이 필요한가?
| |
| +-- 아니오 (순수 API 권한 위임, 서비스 간 호출)
| | --> OAuth 2.0 (machine-to-machine이면 Client Credentials,
| | OAuth 2.1 제약 준수, RFC 9700 적용)
| |
| +-- 예
| |
| +-- Q2. 연동 대상이 우리가 만드는 앱인가,
| | 외부 고객사의 기존 IdP인가?
| |
| +-- 외부 고객사 IdP와 B2B 연동
| | |
| | +-- 고객사가 OIDC를 지원? --> OIDC (우선)
| | +-- 고객사가 SAML만 지원? --> SAML 2.0
| | (엔터프라이즈 현실: SAML만 되는 곳이 여전히 많음)
| |
| +-- 우리가 만드는 앱 (자체 IdP/소셜 로그인)
| |
| +-- 웹/SPA/모바일/데스크톱 무엇이든
| --> OIDC Authorization Code + PKCE
| (2026년 신규 구축의 기본값)
|
+-- Q3. 로그인 후 API 호출도 필요한가?
--> OIDC로 로그인 + 같은 흐름에서 받은 access token으로
OAuth 2.0 리소스 접근 (둘은 배타적이 아니라 보완재)
요약하면 이렇습니다.
- **신규 구축은 OIDC가 기본값**입니다. SAML을 새로 도입할 이유는 "상대방이 SAML만 지원해서"가 거의 유일합니다.
- **OAuth 2.0 단독**은 로그인 없는 순수 인가(서비스 계정, API 위임)에 씁니다.
- **SAML**은 레거시가 아니라 "엔터프라이즈 B2B의 공용어"로서 여전히 1군입니다. 다만 새 기능 투자는 OIDC 진영에서 일어납니다.
엔터프라이즈 vs B2C — 선택 기준 정리
| 기준 | 엔터프라이즈 (직원/B2B) | B2C (소비자) |
| --- | --- | --- |
| 1순위 프로토콜 | SAML 또는 OIDC (상대 IdP에 맞춤) | OIDC |
| IdP | 회사 IdP (Entra ID, Okta, Keycloak 등) | 자체 OP + 소셜 로그인 |
| 사용자 수명주기 | HR 연동, SCIM(RFC 7644) 프로비저닝 | 셀프 가입, 이메일/전화 검증 |
| 인증 수단 | 사내 정책 (MFA 강제, 디바이스 신뢰) | passkeys 우선, 소셜 위임 |
| 세션 정책 | 짧게, idle timeout 엄격 | 길게, 마찰 최소화 |
| 로그아웃 | SLO 요구 빈번 | 단일 앱 로그아웃이면 충분한 경우 多 |
| 규제 | SOC 2, ISO 27001, 산업 규제 | 개인정보보호법, GDPR |
엔터프라이즈 SSO에서 자주 잊는 부분이 **프로비저닝**입니다. SSO는 "로그인"만 해결할 뿐, 계정 생성/권한 부여/퇴사자 비활성화는 별도 문제이며 SCIM이 그 표준입니다. "퇴사자가 SaaS에 여전히 로그인 가능"한 사고의 대부분은 SSO가 아니라 프로비저닝 부재 때문입니다.
2026년의 관점 — OAuth 2.1, passkeys, 그리고 그 너머
OAuth 2.1 제약을 지금 적용하기
OAuth 2.1이 RFC가 되기를 기다릴 필요가 없습니다. 오늘 할 일은 다음과 같습니다.
체크리스트 (OAuth 2.1 정합성)
[ ] 모든 클라이언트에 PKCE(S256) 적용 — confidential client 포함
[ ] Implicit Flow 사용 중인 SPA를 Code + PKCE로 마이그레이션
[ ] ROPC(password grant) 전면 폐기 — 테스트 코드도 예외 없음
[ ] access token을 URL 쿼리로 전달하는 코드 제거
[ ] public client의 refresh token에 rotation 적용
[ ] redirect_uri는 정확 일치(exact match)로만 등록
passkeys와 SSO의 관계
passkeys는 SSO 프로토콜의 경쟁자가 아니라 **IdP 내부의 1차 인증 수단**입니다. 구조는 다음과 같이 계층화됩니다.
[앱들] --(OIDC/SAML: 인증 결과 전달)--> [IdP] --(WebAuthn: 사용자 실제 인증)--> [passkey]
IdP가 passkeys를 도입하면 연동된 모든 앱이 코드 변경 없이 피싱 저항 인증의 혜택을 받습니다. 이것이 SSO 아키텍처의 진짜 가치입니다. Keycloak 26.6의 로그인 폼 passkeys 통합(conditional UI)이 좋은 예입니다.
그 외 주목할 흐름
- **FAPI 2.0**: 금융급 보안 프로파일이 Final이 되었고 Keycloak 26.6이 FAPI 2.0 Security Profile과 Message Signing을 지원합니다. 고보안 도메인이라면 FAPI 2.0 프로파일 채택을 검토하세요.
- **AI 에이전트와 위임**: 에이전트가 사용자를 대신해 API를 호출하는 패턴은 정확히 OAuth의 위임 모델입니다. Token Exchange(RFC 8693)와 CIMD 같은 스펙이 이 영역에서 빠르게 발전하고 있습니다.
- **Verifiable Credentials**: 발급자-보유자-검증자 모델의 분산 신원이 SSO의 장기적 대안으로 논의되지만, 2026년 시점에서 엔터프라이즈 주류는 여전히 OIDC/SAML입니다.
실전 예제 — 하나의 IdP에 OIDC와 SAML 클라이언트 나란히 등록하기
개념을 손에 익히는 가장 빠른 방법은 같은 IdP(여기서는 Keycloak 26.6)에 두 프로토콜의 클라이언트를 나란히 등록해 보는 것입니다.
먼저 OIDC 쪽. Spring Boot 앱이라면 application.yml에 issuer 하나만 지정하면 Discovery가 나머지 엔드포인트를 모두 해결합니다.
spring:
security:
oauth2:
client:
registration:
keycloak:
client-id: web-dashboard
client-secret: CHANGE_ME
authorization-grant-type: authorization_code
scope: openid,profile,email
redirect-uri: "https://app.example.com/login/oauth2/code/keycloak"
provider:
keycloak:
issuer-uri: https://idp.corp.com/realms/prod
다음으로 Keycloak 관리 CLI(kcadm)로 두 클라이언트를 만들어 봅니다.
OIDC 클라이언트 (신규 웹 앱)
/opt/keycloak/bin/kcadm.sh create clients -r prod \
-s clientId=web-dashboard \
-s protocol=openid-connect \
-s 'redirectUris=["https://app.example.com/login/oauth2/code/keycloak"]' \
-s publicClient=false \
-s standardFlowEnabled=true \
-s 'attributes={"pkce.code.challenge.method":"S256"}'
SAML 클라이언트 (레거시 B2B SaaS)
/opt/keycloak/bin/kcadm.sh create clients -r prod \
-s 'clientId=https://legacy.example.com/saml/metadata' \
-s protocol=saml \
-s 'redirectUris=["https://legacy.example.com/saml/acs"]' \
-s 'attributes={"saml.signature.algorithm":"RSA_SHA256","saml_assertion_consumer_url_post":"https://legacy.example.com/saml/acs"}'
이렇게 두 앱을 연동하면 흥미로운 일이 벌어집니다. **같은 사용자, 같은 IdP SSO 세션**인데, 한쪽 앱은 JWT(ID Token)를 받고 다른 쪽 앱은 XML Assertion을 받습니다. 사용자가 OIDC 앱에 먼저 로그인한 뒤 SAML 앱에 접근하면 로그인 화면 없이 즉시 Assertion이 발급됩니다. IdP가 **프로토콜 변환 허브** 역할을 한다는 것, 그리고 SSO 세션이 프로토콜보다 한 층 아래에 있다는 것을 직접 체감할 수 있는 실험입니다.
운영/보안 베스트 프랙티스
1. **토큰 수명은 짧게, 갱신은 refresh token으로** — access token 5~15분, refresh token은 rotation과 함께. ID Token은 로그인 직후 검증 용도로만 쓰고 저장하지 않습니다.
2. **state와 nonce는 항상** — state는 CSRF 방어, nonce는 ID Token 재사용 방어입니다. 라이브러리가 해주는지 "확인"까지 하세요.
3. **시계 동기화** — SAML Assertion과 JWT 모두 시간 조건이 있습니다. NTP가 어긋난 서버 한 대가 간헐적 로그인 실패를 만듭니다. 검증 시 60~120초의 clock skew 허용치를 설정하세요.
4. **키/인증서 수명주기 관리** — SAML 인증서 만료와 OIDC 서명 키 회전은 캘린더와 자동화로 관리합니다. JWKS 캐시 TTL을 적절히(예: 몇 분~몇 시간) 설정해 키 회전 시 무중단이 되게 합니다.
5. **로그아웃 설계를 처음부터** — "어디까지 로그아웃되어야 하는가"(앱 세션만? IdP SSO 세션까지? 모든 앱까지?)를 요구사항으로 명문화하세요. 나중에 붙이기 가장 어려운 기능입니다.
6. **IdP 이중화와 장애 대응** — IdP가 죽으면 모든 앱의 로그인이 죽습니다. IdP는 가장 높은 가용성 등급으로 운영하고, Keycloak 26.6의 zero-downtime rolling patch update 같은 기능을 활용하세요.
자주 만나는 안티패턴
| 안티패턴 | 무엇이 문제인가 | 해법 |
| --- | --- | --- |
| access token으로 로그인 판단 | 수신자 검증 부재, 토큰 치환 공격 | ID Token 검증으로 로그인 판단 |
| Implicit Flow 신규 사용 | 토큰이 URL fragment에 노출 | Code + PKCE |
| ROPC로 자사 앱 로그인 | 피싱 학습 효과, MFA/passkey 불가 | 시스템 브라우저 + Code + PKCE |
| JWT를 localStorage에 저장 | XSS 한 방에 토큰 전부 유출 | httpOnly 쿠키 세션 또는 BFF 패턴 |
| SAML 서명 검증 생략/부분 검증 | Assertion 위조, Signature Wrapping | 검증된 라이브러리 + Response/Assertion 모두 서명 |
| redirect_uri 와일드카드 등록 | open redirect로 코드 탈취 | exact match만 허용 |
| 영원히 사는 refresh token | 탈취 시 무기한 세션 | rotation + 재사용 감지 |
| SSO만 하고 SCIM 생략 | 퇴사자 계정 잔존 | SCIM 프로비저닝/디프로비저닝 |
마치며
세 프로토콜의 관계를 마지막으로 한 번 더 압축하면 이렇습니다.
- **OAuth 2.0**은 "권한을 위임하는 방법"입니다. 인증 프로토콜이 아닙니다.
- **OIDC**는 OAuth 2.0 위에 "너는 누구인가"를 표준으로 얹은 것이며, 2026년 신규 구축의 기본값입니다.
- **SAML 2.0**은 엔터프라이즈 B2B SSO의 공용어로 여전히 현역이지만, 새로 선택할 이유는 호환성뿐입니다.
- **OAuth 2.1**은 새 프로토콜이 아니라 "이제는 상식이 된 보안 수칙의 성문화"입니다. 오늘부터 따르면 됩니다.
다음 글에서는 SAML 2.0의 내부(Assertion, Binding, Metadata)를, 그다음 글에서는 OIDC의 내부(Code Flow, Discovery, 토큰 검증)를 코드 수준으로 파고듭니다.
참고 자료
- [RFC 6749 — The OAuth 2.0 Authorization Framework](https://datatracker.ietf.org/doc/html/rfc6749)
- [RFC 7636 — Proof Key for Code Exchange (PKCE)](https://datatracker.ietf.org/doc/html/rfc7636)
- [RFC 9700 — Best Current Practice for OAuth 2.0 Security](https://datatracker.ietf.org/doc/html/rfc9700)
- [OAuth 2.1 Draft (draft-ietf-oauth-v2-1)](https://datatracker.ietf.org/doc/draft-ietf-oauth-v2-1/)
- [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 7519 — JSON Web Token (JWT)](https://datatracker.ietf.org/doc/html/rfc7519)
- [RFC 7644 — SCIM Protocol](https://datatracker.ietf.org/doc/html/rfc7644)
- [RFC 8693 — OAuth 2.0 Token Exchange](https://datatracker.ietf.org/doc/html/rfc8693)
- [FAPI 2.0 Security Profile](https://openid.net/specs/fapi-2_0-security-profile.html)
- [W3C WebAuthn Level 3](https://www.w3.org/TR/webauthn-3/)
- [FIDO Alliance — Passkeys](https://fidoalliance.org/passkeys/)
- [Keycloak Documentation](https://www.keycloak.org/documentation)
- [Keycloak Release Notes](https://www.keycloak.org/docs/latest/release_notes/index.html)
현재 단락 (1/261)
사내 시스템이 10개를 넘어가는 순간, 그리고 SaaS 도입이 본격화되는 순간, 모든 조직은 같은 질문에 부딪힙니다. "사용자가 비밀번호를 도대체 몇 개나 기억해야 하는가?" Si...