SAML 메시지를 디코딩하는 방법
- 가진 것을 그대로 붙여넣으세요. 다음은 모두 됩니다.
- URL 인코딩 여부와 상관없이
SAMLResponse또는SAMLRequest값만 RelayState,SigAlg,Signature가 있거나 없는SAMLRequest=…- 주소 표시줄에서 복사한 Redirect URL 전체
- 숨은
SAMLResponse입력이 있는 HTML 자동 제출 폼 - XML 원문
- URL 인코딩 여부와 상관없이
- 디코더가 바인딩을 감지합니다. 일반 Base64면 HTTP-POST, deflate로 압축된 데이터면 HTTP-Redirect입니다.
- 먼저 요약을 읽고, 그 아래 보기 좋게 정렬된 XML을 확인하세요. XML은 텍스트로만 표시되고 렌더링되지 않습니다.
인코딩 방향은 반대로 동작합니다. AuthnRequest를 붙여넣으면 Redirect 값, SAMLRequest= 파라미터, 또는 Destination 속성으로 만든 전체 URL을 받습니다. 서비스 제공자를 테스트하면서 요청을 다시 보낼 때 편리합니다.
Redirect 바인딩과 POST 바인딩
| HTTP-Redirect | HTTP-POST | |
|---|---|---|
| 전송 방식 | GET 요청의 쿼리 문자열 | 자동 제출되는 숨은 폼 필드 |
| 인코딩 | raw DEFLATE, 그다음 Base64, 그다음 URL 인코딩 | Base64만 |
| 일반적인 메시지 | AuthnRequest, LogoutRequest | 어설션이 담긴 Response |
| 서명 | 별도의 SigAlg와 Signature 파라미터 | 메시지 안의 XML 서명 |
| 알아보는 방법 | 고정 접두사 없음, Base64 디코딩 후 바이너리 | PHNhbWxwOl (<samlp:), PHNhbWwycDp (<saml2p:), PD94bWwg (<?xml ) |
URL에는 현실적인 길이 제한이 있어서 Redirect는 압축을 씁니다. 이 페이지의 예시에서 464자짜리 AuthnRequest는 deflate로 273바이트가 되고 Base64 364자로 전송됩니다. 응답에는 서명과 인증서가 들어 있어 URL에 담기에는 너무 크므로 POST로 보냅니다. 일부 서비스 제공자는 Redirect에서 raw deflate 대신 zlib으로 감싼 데이터를 보내는데, 디코더는 이것도 받아들입니다.
SSO가 실패할 때 확인할 것
요약에는 로그인 실패의 대부분을 일으키는 필드가 정리되어 있습니다.
- Status:
urn:oasis:names:tc:SAML:2.0:status:Success가 아니면 문제입니다.AuthnFailed,InvalidNameIDPolicy,RequestDenied같은 하위 코드가 원인을 알려 주며,StatusMessage에 세부 정보가 더 있는 경우가 많습니다. - Destination과 ACS URL:
https와 끝의 슬래시까지 포함해 서비스 제공자에 설정된 엔드포인트와 정확히 같아야 합니다. - Audience: SP 엔터티 ID와 같아야 합니다. 애플리케이션 이름을 바꾼 뒤 여기서 불일치가 생기는 설정 오류가 흔합니다.
- Conditions:
NotBefore와NotOnOrAfter는 시스템 시계를 기준으로 확인합니다. 방금 로그인했는데 “만료됨"이나 “아직 유효하지 않음"이 나오면 IdP와 SP 사이의 시계 차이를 의심하세요. - InResponseTo:
AuthnRequest의ID와 같아야 합니다. 다르면 오래되었거나 재전송된 응답일 수 있습니다. IdP에서 시작한 로그인에는 이 속성이 없으며, 일부 서비스 제공자는 이를 거부합니다. - NameID와 Format: SP가 사용자와 대조할 식별자입니다. 예를 들어
emailAddress인지persistent인지 확인하세요. - 속성: 모든 속성과 그 값 전체가 표시되므로, SP가 매핑하는 그룹이나 이메일을 IdP가 보내는지 확인할 수 있습니다.
Python에서 SAML 디코딩
import base64, zlib
from urllib.parse import unquote
def decode_post(value):
return base64.b64decode(value).decode()
def decode_redirect(value):
raw = base64.b64decode(unquote(value))
return zlib.decompress(raw, -15).decode()
-15는 zlib에 헤더 없는 raw deflate를 기대하라고 알려 줍니다. unquote는 URL에서 복사한 값에만 쓰세요. POST 폼의 값은 이미 일반 Base64이며, URL 디코딩하면 +가 공백으로 바뀔 수 있습니다. 자세한 내용은 Python에서 Base64에 있고, 압축 쪽은 raw deflate와 zlib 페이지에서 설명합니다.
SAML 어설션과 개인정보
어설션에는 이름, 이메일 주소, 그룹 멤버십, 때로는 그 이상의 개인정보가 들어 있으며, 만료되지 않은 유효한 어설션은 로그인 토큰 역할을 할 수 있습니다. 이 페이지는 로컬에서 디코딩하고 어디로도 보낼 수 없습니다. 그래도 티켓이나 채팅에 붙여넣기 전에는 민감한 부분을 가리고, 캡처한 응답은 NotOnOrAfter가 지날 때까지 세션 쿠키처럼 다루세요.
자주 묻는 질문
브라우저에서 SAML 응답을 캡처하려면 어떻게 하나요?
개발자 도구를 열고 Network 탭에서 Preserve log를 켠 뒤 로그인하세요. 서비스 제공자의 ACS URL로 가는 POST 요청을 찾아 페이로드의 SAMLResponse 폼 필드를 복사해 여기에 붙여넣으면 됩니다.
SAMLRequest가 일반 Base64 디코더로 디코딩되지 않는 이유는 무엇인가요?
HTTP-Redirect 바인딩에서는 XML을 raw deflate로 압축한 다음 Base64와 URL 인코딩을 합니다. 일반 디코더로는 알아볼 수 없는 바이너리만 보입니다. 이 도구는 URL 디코딩, Base64 디코딩, 압축 해제를 한 번에 합니다.
디코더가 SAML 서명을 검증하나요?
아니요. 메시지, 어설션, 쿼리 문자열에 서명이 있는지는 알려 주지만 IdP 인증서로 서명을 검증하지는 않습니다. 서명 검증은 서비스 제공자가 할 일입니다.
EncryptedAssertion을 복호화할 수 있나요?
아니요. 암호화된 어설션에는 서비스 제공자의 개인 키가 필요합니다. 디코더는 그 개수를 세고, 암호화되지 않은 외부 정보인 status, issuer, destination은 그대로 보여 줍니다.