본문으로 건너뛰기
김신건의 로그

[Auth] SAML 2.0: XML 기반 기업 SSO

· 수정 · 📖 약 2분 · 746자/단어 #saml #sso #enterprise #auth #security
SAML, SAML 2.0, SP-initiated, IdP-initiated, SAML Assertion, AuthnRequest, Metadata

정의

SAML 2.0 (Security Assertion Markup Language) 는 XML 기반 기업 SSO 표준. 2005년 OASIS 표준화. 모바일 친화도 / 단순성에서 OIDC 에 밀리지만 기업 / SaaS 통합의 옛 표준 으로 여전히 광범위.

핵심 역할

flowchart LR
    User --> SP["SP (Service Provider)<br/>예: Salesforce"]
    User --> IdP["IdP (Identity Provider)<br/>예: Okta"]
    SP --> IdP
    IdP --> SP
역할설명예시
SP (Service Provider)서비스를 제공, IdP 에 인증 위임Salesforce, Slack, GitHub Enterprise
IdP (Identity Provider)신원 확인 + Assertion 발급Okta, Azure AD, Google Workspace

SP-initiated SSO 흐름

sequenceDiagram
    autonumber
    participant U as User
    participant SP as ServiceProvider
    participant IdP as IdentityProvider

    U->>SP: GET /app (로그인 안 됨)
    SP->>U: 302 Redirect to IdP (SAMLRequest base64+deflate)
    U->>IdP: SAMLRequest 전달
    IdP->>U: 로그인 화면 표시
    U->>IdP: id + password 입력
    IdP->>IdP: SAML Response 생성 (Assertion + XML 서명)
    IdP->>U: HTML form 자동 submit (SAMLResponse)
    U->>SP: POST /acs (SAMLResponse)
    SP->>SP: Assertion 검증 (서명, 유효시간, Audience)
    SP-->>U: 세션 시작, 애플리케이션 접근 허용

SP-initiated = 서비스 (SaaS) 가 먼저 redirect. 가장 일반적.

IdP-initiated SSO 흐름

sequenceDiagram
    autonumber
    participant U as User
    participant IdP as IdentityProvider
    participant SP as ServiceProvider

    U->>IdP: IdP 포털에서 앱 아이콘 클릭
    IdP->>IdP: SAML Response 생성 (InResponseTo 없음)
    IdP->>U: HTML form 자동 submit (SAMLResponse)
    U->>SP: POST /acs (SAMLResponse)
    SP->>SP: Assertion 검증 (InResponseTo 검증 생략됨)
    SP-->>U: 세션 시작

WARNING

IdP-initiated 는 InResponseTo 가 없어 CSRF replay 공격에 취약. SP 가 허용 여부 명시 설정 필요.

HTTP Binding 비교

Binding전송 방법특징
HTTP-RedirectURL query string (deflate + base64)GET 요청, URL 길이 한계 (~2KB)
HTTP-POSTHTML form hidden field (base64)POST 요청, 크기 제한 없음, XML 서명 전송 가능

AuthnRequest 는 대부분 HTTP-Redirect. SAMLResponse 는 반드시 HTTP-POST (서명 포함 크기 때문).

# HTTP-Redirect (AuthnRequest)
GET /sso?SAMLRequest=nJJBj9MwEIXv...&SigAlg=RSA-SHA256&Signature=abc...

# HTTP-POST (SAMLResponse)
POST /acs
Content-Type: application/x-www-form-urlencoded

SAMLResponse=PHNhbWxwO...

SAML Assertion (XML)

<saml:Assertion ID="..." IssueInstant="2026-06-25T12:00:00Z">
  <saml:Issuer>https://idp.example.com</saml:Issuer>
  <ds:Signature>...</ds:Signature>
  <saml:Subject>
    <saml:NameID Format="...:emailAddress">koa@example.com</saml:NameID>
    <saml:SubjectConfirmation Method="bearer">
      <saml:SubjectConfirmationData
        Recipient="https://sp.example.com/acs"
        NotOnOrAfter="2026-06-25T12:05:00Z"
        InResponseTo="<원래 AuthnRequest ID>"/>
    </saml:SubjectConfirmation>
  </saml:Subject>
  <saml:Conditions
    NotBefore="2026-06-25T12:00:00Z"
    NotOnOrAfter="2026-06-25T12:05:00Z">
    <saml:AudienceRestriction>
      <saml:Audience>https://sp.example.com</saml:Audience>
    </saml:AudienceRestriction>
  </saml:Conditions>
  <saml:AuthnStatement AuthnInstant="2026-06-25T12:00:00Z">
    <saml:AuthnContext>
      <saml:AuthnContextClassRef>
        urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
      </saml:AuthnContextClassRef>
    </saml:AuthnContext>
  </saml:AuthnStatement>
  <saml:AttributeStatement>
    <saml:Attribute Name="email">
      <saml:AttributeValue>koa@example.com</saml:AttributeValue>
    </saml:Attribute>
    <saml:Attribute Name="department">
      <saml:AttributeValue>engineering</saml:AttributeValue>
    </saml:Attribute>
  </saml:AttributeStatement>
</saml:Assertion>

Metadata 교환

SP 와 IdP 가 XML metadata 파일 로 사전 합의:

항목SP metadataIdP metadata
Entity IDhttps://sp.example.comhttps://idp.example.com
ACS URLhttps://sp.example.com/acs-
SSO URL-https://idp.example.com/sso
SLO URLlogoutlogout
Certificate서명 검증용 공개키동일

대부분의 SaaS 는 URL 한 줄로 metadata 자동 교환.

SLO (Single Logout)

sequenceDiagram
    participant U as User
    participant SP as ServiceProvider
    participant IdP as IdentityProvider

    U->>SP: POST /logout
    SP->>U: Redirect to IdP (LogoutRequest)
    U->>IdP: LogoutRequest 전달
    IdP->>IdP: 세션 종료, 다른 SP 에 LogoutRequest 전파
    IdP-->>U: LogoutResponse (redirect to SP)
    U->>SP: LogoutResponse 전달
    SP-->>U: 로그아웃 완료

SLO 는 모든 SP 의 세션을 한 번에 종료. 구현이 복잡해 미구현 SP 도 많음.

SAML vs OIDC 선택

flowchart TD
    Q1{타겟 사용자}
    Q1 -->|기업 직원 SSO| Q2{기존 IdP?}
    Q1 -->|소비자 / 모바일| OIDC[OIDC / OAuth2 권장]
    Q2 -->|Okta, Azure AD, Ping| Q3{새 앱?}
    Q2 -->|Google, GitHub| OIDC
    Q3 -->|예| OIDC_ENT["OIDC 권장<br/>(단순, 모바일 지원)"]
    Q3 -->|기존 SAML SP 통합| SAML[SAML 유지]
항목SAML 2.0OIDC
출시20052014
페이로드XMLJSON (JWT)
크기큼 (수 KB)작음
모바일떨어짐우수
디버깅복잡 (XML 서명)단순
기업 SSO전통적부상 중
MFA / step-up가능 (acr)가능 (acr)
사용자 의도토큰 발급 후 세션토큰 자체 반복 사용

IMPORTANT

2026 시점: 모바일 / 소비자 앱 = OIDC. 기업 SaaS 통합 (Workday, Salesforce, ServiceNow, Slack 등) = 여전히 SAML 이 압도적.

보안: Assertion 검증 체크리스트

검증의미
SignatureXML-DSig 로 IdP 공개키 검증
Audience내가 그 audience 인지
NotBefore / NotOnOrAfter유효 시간
Issuer신뢰하는 IdP 인지
InResponseTo원래 보낸 AuthnRequest ID 와 일치
ReplayNotOnOrAfter 까지 받은 ID 중복 거절

XSW (XML Signature Wrapping) 공격

XML 서명 검증 구현 버그를 이용해 서명된 영역과 실제 처리 영역을 다르게 만드는 공격:

<!-- 공격자가 조작한 Assertion (개념도) -->
<saml:Assertion ID="evil" ...>
  <!-- 공격자가 원하는 내용 (서명 없음) -->
  <saml:NameID>admin@company.com</saml:NameID>
  <!-- 원본 서명된 Assertion 은 다른 위치에 숨겨짐 -->
</saml:Assertion>

방어: 검증된 XML 라이브러리 사용. Assertion ID 의 canonicalization 엄격 검증. 자체 XML 파서 구현 금지.

단점 / 함정

WARNING

  1. XML Canonicalization 의 함정 = 공백 처리 까지 정확해야 서명 검증. 라이브러리 버그 다수 (XSW 공격).
  2. 시계 어긋남 = NotBefore / NotOnOrAfter 검증에서 수십 초 clock skew 가 문제. NTP 필수.
  3. 메시지 크기 = HTTP-Redirect 으로 보내는 인코딩된 SAMLRequest 가 URL 길이 한계 초과 가능. HTTP-POST binding 사용.
  4. Assertion 의 audience 검증 누락 = 다른 SP 의 assertion 이 재사용 가능.
  5. IdP-initiated CSRF = InResponseTo 없으므로 replay 가능. SP 에서 허용 여부 명시.

Okta 설정 예시

실제 SaaS 통합에서 SP 설정 예시:

# Okta 앱 설정 (개념도)
SingleSignOnURL: https://app.example.com/auth/saml/callback  # ACS URL
Audience URI (Entity ID): https://app.example.com
NameID format: EmailAddress
Attribute Statements:
  - email: user.email
  - firstName: user.firstName
  - lastName: user.lastName
  - groups: user.groups
# python3-saml 라이브러리로 Assertion 검증
from onelogin.saml2.auth import OneLogin_Saml2_Auth

def prepare_django_request(request):
    return {
        "https": "on" if request.is_secure() else "off",
        "http_host": request.META["HTTP_HOST"],
        "script_name": request.META["PATH_INFO"],
        "post_data": request.POST.dict(),
    }

def acs(request):
    req = prepare_django_request(request)
    auth = OneLogin_Saml2_Auth(req, custom_base_path=settings.SAML_FOLDER)
    auth.process_response()
    errors = auth.get_errors()
    if not errors:
        user_email = auth.get_nameid()
        attributes = auth.get_attributes()
        # 세션 생성 로직

관련 위키

이 글의 용어 (4개)
[Auth] JWT: 구조, 서명, 만료, refresh tokenauth-security
정의 JWT (JSON Web Token) (RFC 7519) 은 base64url 인코딩된 JSON + 서명 형태의 self-contained token. 서버가 세션 저장 없…
[Auth] OAuth 2.0 / 2.1: Authorization Code + PKCEauth-security
정의 OAuth 2.0 (RFC 6749, 2012) 은 제3자 앱이 사용자 동의로 자원에 접근 하게 하는 권한 위임 프레임워크. 인증 (authentication) 이 아니라 …
[Auth] OpenID Connect (OIDC): OAuth 위의 인증 레이어auth-security
정의 OpenID Connect (OIDC) 는 OAuth 2.0 위에 얹은 인증 (authentication) 표준. OAuth 가 권한 위임 (authorization), O…
[Auth] Session Cookie: HttpOnly, SameSite, Secure flagauth-security
정의 Session Cookie 는 서버가 발급한 임의 ID 를 쿠키에 보관. 매 요청 자동 첨부. 서버 측 세션 store (Redis, DB) 와 짝. [!IMPORTANT]…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기