- Published on
SAML 프로토콜의 이해
- Authors
- Name
- sulmo
1. SSO(Single Sign-On)
1.1 개요
- 한 번의 로그인(인증)으로 여러 시스템(서비스)에 재 인증 절차 없이 접근할 수 있는 통합 로그인 솔루션
- 다른 사이트(또는 앱)에서 로그인 및 인증 부분만 따로 사용하는 것 (API, 모듈 등)
- (보통) 기업에서 일반적으로 사용되며, 사용자가 인증 정보를 한 번만 제공하면 여러 개의 애플리케이션에 대해 로그인할 수 있도록 합니다
- (보통) 기업에서 사용자 인증 및 보안을 강화하기 위한 목적으로 사용되며, 보안성 측면에서 이점이 있습니다
Social Login
- 사용자가 다른 웹 사이트 또는 애플리케이션에서 사용하는 소셜 미디어 계정을 통해 인증을 수행하는 것이 목적입니다
- 사용자가 소셜 미디어 계정으로 인증하면 해당 서비스(소셜 미디어)의 API를 사용하여 사용자 정보를 가져올 수 있도록 합니다
- 사용자가 소셜 미디어 계정 정보를 사용하여 인증하는 것이므로, 사용자 경험 측면에서 이점이 있습니다. 사용자는 새로운 계정을 만들거나 암호를 기억하거나 관리할 필요가 없으므로, 인증 및 로그인 절차가 더욱 간편합니다
2. SAML(Security Assertion Markup Language)
2.1 SAML 개요
구성요소
Identity Provider
사용자를 인증(Authentication)을 해주는 인증 제공자입니다.
사용자의 신원을 확인하고, 확인한 데이터(Assertion)를 SP에게 넘겨줍니다.
Service Provider
사용자의 권한을 확인해주는 곳을 말합니다. 인증은 하지 않고 서비스를 제공하는 곳이므로 IdP로부터 받은 assertion을 확인해 권한을 부여하는 역할을 합니다.
(저희는 권한 설정까지 제공하지 않기 때문에 단순히 IdP로부터 인증만 받습니다)
User Agent
브라우저를 말합니다
- SAML은 IDP와 SP 간의 authenticate 및 authorization 데이터를 교환하기 위한 XML 기반 표준 데이터 포맷(프로토콜)
- SAML은 인증 정보를 XML 포맷으로 생성하고 이를 암호화함으로써, 제 3자에게 내용을 노출시키지 않고 최종 수신자까지 전달 가능 → 이때 생성한 XML을 Assertion이라고 함
- XML Assertion (Assertion이란, "주장" 또는 "진술"을 의미합니다. SAML Assertion은 IdP가 SP에게 사용자 인증 정보를 "주장"하거나 "진술"하는 것입니다) → 그냥 인증 끝났고 인증된 유저에 대한 정보를 담아 보내준다로 보는게 편합니다
2.2 SAML Flow
SAML 초기 Flow ⇒ 어떤식으로 SSO가 가능한가에 대한 설명입니다.

- SAML Federation을 구성할 때는 IDP와 SP간의 신뢰(TRUST) 관계 설립이 필요 함 ⇒ 신뢰(trust)를 가진다는건 서로의 메타데이터(Entrypoint URL, Logout URL, Signing Certificate.. etc)를 공유하고 → (SP와 IDP가 metadat url 공유)
- 사용자가 SP에 Access하기 위해서는 먼저 IDP에 authenticate 해야함
- authenticate(인가)를 성공하고, authorized(권한인증)이 된 경우, IDP는 SAML Assertion을 generate함
- Assertion이 SP의 Application으로 전송되고, Application은 IDP를 신뢰하기 때문에 User는 Access가 allowed됨
- 또한 user는 IDP에 인증되었기 때문에 다른 Application들로도 SSO 가능
조금만 더 깊게 보자면

- IDP는 user의 속성을 알고 있음
- SP는 user의 정보를 알고 있음 (회원)
- IDP가 Assertion을 generate할 때 Assertion은 user Identifier(식별자)로 채워짐. 그리고 그것을 SP로 보냄
- SP는 assertion을 validate 해야함. 하지만, clear text로 전달되지 않거나(암호화되어있는) 완전히 unprotected하지 않음
- 따라서 IDP는 먼저 assertion에 sign을 함. 따라서 SP는 assertion의 issuer(발행자)를 검증할 수 있음
- 그다음 SP는 user의 Identifier를 읽고, 그들의 user store의 정보와 mapping함 → 하지만 user attribute를 찾을 수 없기 때문에 실패
따라서, 우리는 몇가지 integration rule들을 세팅해 줘야합니다.
작아서 잘 안보이지만 위에 이미지에서 보면 요런 config가 있습니다. 어떤 attribute로 유저를 확인할지 명세하여 assertion의 유저정보와 SP의 유저 데이터를 맵핑합니다.
이러한 config의 출처는 다음과 같습니다.
각각 SP와 IdP가 XML형식의 Metadata를 작성합니다. Metadata는 세팅과 인증을 위해 사용됩니다. 그리고 이를 서로 교환하는데 이 과정을 TRUST라고 합니다.
ex) SP는 user의 identifier와 format을 email로 지정할 수 있음 → IDP도 email format으로 match 해야함
SAML 인증 트랜젝션

브라우저가 중개자의 역할을 하여 로그인을 시도합니다. 아래는 옥타 IdP기준 상세 플로우입니다.
- SpaceONE(SP)에서 SAML 로그인 버튼을 클릭
- SP는 딥링크를 생성해 Redirect(옥타 로그인 url)로 응답을 돌려줍니다
- 리다이렉트된 페이지는 옥타에 접근하여 login form을 띄워줍니다
- 이후 사용자가 로그인을 시도해 인증합니다
- IdP는 해당 사용자가 로그인을 성공했다면, SAML assertion을 생성해 브라우저에게 전달합니다
- 브라우저는 우리 백엔드의
/login/callback/
으로 Post요청을 넘기고 - 우리 백엔드 서버는 assertion내용을 검증하고, (우리 서비스기준) email 을 확인해 보안 컨택스트를 반환해줍니다
- 프론트앱이 반환받은 보안 컨택스트(토큰)으로 우리는 다시 백엔드에 리소스를 요청합니다
IdP initiate? SP initiate?
위에 플로우 이미지를 보시면 두가지 시작점이 존재합니다.
IdP 대시보드에서 인증 플로우가 시작되는 경우도 있고, 서비스에서 로그인을 진행하여 인증 플로우가 진행되는경우가 있습니다.
위 두가지를 각각 IdP initiate, SP initiate 이라고 부릅니다.
3. More about SAML
3.1 SAML 장점
- 사용자 경험 개선 → 생산성 개선
- 자격증명 분실 감소
- 보안 강화
- 비용 절감
- 사용자 관리 간소화
3.2 SAML 단점
- SAMLRequest, SAMLResponse의 포맷은 XML 형식이라 브라우저를 통해서만 동작 가능 → 모바일 or Native Application에서는 부적절
3.3 SAML 대체 솔루션
OAuth
- Authorization을 위한 개방형 표준 프로토콜
- Third-Party App에게 리소스 소유자를 대신하여 리소스 서버에서 제공하는 자원에 대한 접근 권한을 위임하는 방식 제공
- 핵심: Access Token + Refresh Token
- SAML과 다르게 XML이 아니라 JSON 기반 포맷 사용
OpenID (OIDC: OpenID Connect)
- OAuth 2.0을 이용해 만들어진 인증 레이어
- OAuth에서 불가능했던 Authentication을 위해 ID Token 이라는 토큰을 추가
- Access Token(OAuth): Bearer 형식 / ID Token: JWT 형식
4. SAML vs OIDC
4.1 차이점
- SAML이 비교적 일찍 나온 프로토콜로 이미 기업에서 많이 사용중이며 신뢰성이 높고 인증 및 권한 부여 처리에 적합하다
- OIDC는 JWT를 사용한 REST API 통신 방식으로 가볍고 빠르며 확장성이 좋다
- OIDC가 모바일에서도 사용이 가능하여 사용자 경험에 좋은편이다
요러한 점들을 고려해 알맞게 선택하면 되는데, 기업들이 saml을 이용한 sso 구축이 이미 되어있다보니 대응하는게 아닐까.. 라는 생각 sso도 그렇구여
4.2 SAML 및 OIDC를 사용해야 하는 경우
- SSO 구현의 기회 제공 → 직원 생산성 높이기
- 서로가 대안이라기 보다는 함께 사용할 수 있는 기술에 더 가까움
ex) MS 환경에서는 OAuth가 authorization(권한 인증)을, SAML이 authentication(인가)을 처리한다고 합니다
4.3 추가 결론
SAML의 경우 이미 많은 기업에서 사용중인 프로토콜로 SP입장에서 지원해줄 수 밖에 없다.(SAML안돼요? 어 그럼 우린 PoC 몬하는데~) 보안측에서도 확장성이나 취약점이 OIDC가 더 낮지만, 이미 보안 시스템이 구축된 기업들에게 OIDC로 마이그레이션이 더 큰 비용이며, 교체하지 않아도 문제없는(?) 상황이라 유지되고 있다.
saml의 취약점 케이스
- XML 서명 우회 공격 (Signature Wrapping): 구조가 복잡해서 XML 내부 조작이 쉬움
- 파싱 취약점: XML 파서는 공격 벡터가 많음
- 시간 기반 토큰 리플레이 공격: 잘못된 설정 시 유효시간 관리에 실패
- 구형 구현체 사용 시 취약점 많음
결국, 큰 기업들에게 Saas로 제공하려면, SP로써 SAML을 통한 로그인 체계를 준비하는 것은 필수이다..
사용자 로그인 시나리오
데모 SAML Request 내용
SAML Metadata
SAML Metadata에는 IdP 및 SP의 기본 정보, SAML 프로토콜 및 바인딩, 인증 방법, 개인 정보 및 보안 설정 등의 정보가 포함됩니다.
이 정보를 참고하여 SAML Request가 생성됩니다.
<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" validUntil="2023-04-20T02:00:48Z" cacheDuration="PT604800S" entityID="http://20.0.106.46:8000/metadata">
<md:SPSSODescriptor AuthnRequestsSigned="false" WantAssertionsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<md:KeyDescriptor use="signing">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate>MIID2zCCAsOgAwIBAgIUZBV/KQw24QoKu0dIXrkvKhmD534wDQYJKoZIhvcNAQELBQAwfTELMAkGA1UEBhMCa3IxDjAMBgNVBAgMBXNlb3VsMQ4wDAYDVQQHDAVzZW91bDEMMAoGA1UECgwDTVpDMQwwCgYDVQQLDANDUEwxFDASBgNVBAMMCzIwLjAuMTA2LjQ2MRwwGgYJKoZIhvcNAQkBFg1taW5vQG16LmNvLmtyMB4XDTIzMDQxMzA0NDUxOFoXDTI0MDQxMjA0NDUxOFowfTELMAkGA1UEBhMCa3IxDjAMBgNVBAgMBXNlb3VsMQ4wDAYDVQQHDAVzZW91bDEMMAoGA1UECgwDTVpDMQwwCgYDVQQLDANDUEwxFDASBgNVBAMMCzIwLjAuMTA2LjQ2MRwwGgYJKoZIhvcNAQkBFg1taW5vQG16LmNvLmtyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvQiUj4EzrOLr0Pr6me8yYPj0VlbEe/dpLiJ9MIN/5E7pCHQTNet5h2qtr+y74/4onrAAZG0JGDIgDS1vvjT3CGiz1EFjgCcda2FgCw58aj92GFY0Zf7DGA+MHFE95/S0Xgn9p2kBVCD/6Nxez4Bn6ntHuv+zDWnB6tOypj6+xwqrvS2kzIVio/1XezO3L82kg3G6GpfVaOv3RijaRQi/1sUbWyoiarlMr41C7U2FCLyM97dRilxrJjaxDEm0bxF2BmyQyAl+YPZDElQKJ3tryOWcV9WZE9fSf9vDco8nOvbkwZwGRDjb20ojHtDYIl3NiF/O6m1goe5wi17nWXwToQIDAQABo1MwUTAdBgNVHQ4EFgQU7fltPaY656+djvcHLnjDI9RLGVcwHwYDVR0jBBgwFoAU7fltPaY656+djvcHLnjDI9RLGVcwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAU52+C5JVQVeztEUPkRshdbkQAJQBEuaSmhCn8aZ3UOzIIJEm9w65IIQXAOj9zUWtA7PcGQ0zHO3S8jp0Ek0xOWoLwviRZ7HEHk047TfX1c4SXeeUMkpT5nYS3T9VmtDf2sAG0u10fs51rNgCtbxr88WzUC1W9fSeqza6nkG3QcljVkppbYOD30HOeKWhbF5jsEvl0rOtDltxyauyO50rq9yDVu6W0tXsuB/f3Ej6mz6cqdhUFLRZB33P9rtg895NAdyYjwyelz8ZqtH9v134uhyaUHRzhnN1m/Hq5XMfTzgg6x6RSlNYk2by5CnwmPy/5TZ/MLHk0YP0S5KOCaZI7Q==</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://20.0.106.46:8000/?sls"/>
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>
<md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://20.0.106.46:8000/login/callback" index="1"/>
</md:SPSSODescriptor>
</md:EntityDescriptor>
- entityID: SP측 고유 식별 아이디
- NameID: 특정 포맷을 요구하지 않는 이상 구지 특정할 필요없고 unspecified를 사용합니다
- AssertionConsumerService: 사용자 인증에 성공하고 생성된 어썰션을 사용할 url입니다
- HTTP-Post binding 으로 지정된 url에 요청합니다
- HTTP-Artifact binding은 SAML 응답 메시지의 크기가 매우 커지는 경우에 유용하게 사용됩니다. 이를 통해 SAML 메시지가 더 큰 수용 능력을 가진 시스템에서 처리될 수 있습니다. SP는 일반적으로 HTTP-Artifact binding을 지원하고, IdP는 이를 선택적으로 사용할 수 있습니다
- SingleLogoutService: 로그아웃 요청 url을 전달할때 사용
- certifiacate: IdP가 데이터를 암호화하기위한 SP의 공개키(SAML은 비대칭키 방식 암호화 사용)
고려사항
MetaData 세팅 가이드? Application 등록 가이드?
- SpaceONE 서비스는 도메인별로 운영되는 독립적인 소프트웨어 벤더(ISV)임으로 각각의 IssuerId(IdP의 entityId)를 도메인별로 지정해줘야하는 형태입니다
- 한 IdP에서 두개의 도메인을 관리하게된다면..?
- 이 외에 추가적인 세팅 사항이 있으면 입력 받고 진행하면 될 것 같습니다
IdP 유저 관리(옥타기준)
유저 추가 및 그룹 생성은 가능 했었고, 그룹 명까지는 SAML Assertion으로 가져올 수 있습니다. 그룹 내부에 유저정보는 Assertion으로 가져올 수 없었고, API로 가져와야 했습니다.(토큰 필요)
트러블 슈팅
IdP에서 인증 이후 SAML assertion을 프론트 앱의 callback/login 페이지로 받아 도메인 id를 함께 서버로 넘기자 라는 설계로 진행하던 중. IdP가 HTTP-Redirect방식으로 assertion을 넘기지 못하는 문제로 설계를 변경하게 됨

(출처: SAML 스펙 문서)
참고
- https://www.youtube.com/watch?v=SvppXbpv-5k&t=1s
- https://devforum.okta.com/t/how-to-integrate-okta-saml-to-vue-spa/12650
- Understanding SAML | Okta Developer
- react + express 데모
- https://techdocs.broadcom.com/us/en/symantec-security-software/web-and-network-security/web-isolation/1-15/Configuring-Security-Policy/Defining-Internal-Users/Defining-SAML-Trust/Defining-SAML-Trust-for_1.html
(여기서 부터는 작업 로그입니다.)
SAML 통신을 위한 IdP 세팅(협상)

- Request Binding: http에 xml을 태울 때, url에 base64로 필요정보를 인코딩(HTTP Redirect)할지, xml파일을 따로 참조해 요청(HTTP Post)할지 정합니다 HTTP POST 요청은 body 부분에 XML을 직접 넣어 전송합니다. 이렇게 함으로써 XML 파일이 노출되는 위험이 줄어들고, 보안성이 높아진다고 합니다
기획사항 질문
- Single Logout: console에서 단순 spaceONE 로그아웃이 아닌 IdP의 전체 로그아웃이 구현되어야하는지?
- 각 IdP 별 SP를 등록하는 가이드를 어떻게 제공해야할지? 구두로? 문서화로?
- SAML 통신은 SP와 IdP의 Metadata 공유가 필요합니다. 여기서 우리 SP측의 metadata를 공유하기위해 필요한 값이 있습니다. domainId를 고객사에게 전달해 줘야 확인이 가능합니다. domainId를 어떻게 전달해야 할까요?
- Google은 유지, KeyClock도 유지?
BE 상세
- deep link 생성 api
- trust ⇒ SAML Assertion validation
/token/issue
api에 SAML Assertion 을 받을 수 있도록 기능 추가
- MetaData
- XML config(Metadata)설정 (BE 구현방식 논의)
- Metadata API 생성, entityId 중복을 어떻게 해결하는가
- entityId의 포맷이
https://{consoleAPI domain}/metadata
와 같은 형식인데, 각 도메인이 다 같은 entityId를 가지는게 문제- 결론: pathParam을 이용함 e.g.)
https://{consoleAPI domain}/metadata/{domainId}
- 결론: pathParam을 이용함 e.g.)
- metadata get요청 만들어야함? Post만 쓴다는 정책 부분적으로 없애도 돼요?
- entityId의 포맷이
FE 상세
- SSO 별 버튼생성 및 SignIn api 연결
- athenticator 구현
- Callback Page 구현 (현재 사용중인 python library로 IdP측에서 콜백으로 Post가 아닌 Redirect요청으로 세팅이 가능한지 확인 필요)
Todo
- find user
- saml 인증
- getByName
- domainId를 getByName으로 가져오고, metadata가져오는거 api 따로 만듦
- 딥링크 만들 때
- IdP의 정보도 필요, metadata도 필요
saml protocol options
- audience (string): The audience of the SAML Assertion. Default will be the Issuer on SAMLRequest.
- recipient (string): The recipient of the SAML Assertion (SubjectConfirmationData). Default is AssertionConsumerUrl on SAMLRequest or Callback URL if no SAMLRequest was sent.
- mappings (object): The mappings between the Auth0 user profile and the output attributes on the SAML Assertion. Each "name" (left side) represents the property name on the Auth0 user profile. Each "value" (right side) is the name (including namespace) for the resulting SAML attribute in the assertion. Default mapping is shown above.
- createUpnClaim (bool): Whether or not a UPN claim should be created. Default is true.
- passthroughClaimsWithNoMapping (bool): If true (default), for each claim that is not mapped to the common profile, Auth0 will passthrough those in the output assertion. If false, those claims won't be mapped. Default is true.
- mapUnknownClaimsAsIs (bool): if passthroughClaimsWithNoMapping is trueand this is false (default), for each claim that is not mapped to the common profile Auth0 will add a prefixhttp://schema.auth0.com. If true it will passthrough the claim as-is. Default is false.
- mapIdentities: If true, it will will add more information in the token like the provider used (google, adfs, ad, etc.) and the access_token if available. Default is true.
- signatureAlgorithm: Signature algorithm to sign the SAML Assertion or response. Default is rsa-sha1and it could be rsa-sha256.
- digestAlgorithm: Digest algorithm to calculate digest of the SAML Assertion or response. defaultsha1. It could be sha256.
- destination: Destination of the SAML Response. If not specified, it will be AssertionConsumerUrlof SAMLRequest or Callback URL if there was no SAMLRequest.
- lifetimeInSeconds (int): Expiration of the token. Default is 3600seconds (1 hour).
- signResponse (bool): Whether or not the SAML Response should be signed. By default the SAML Assertion will be signed, but not the SAML Response. If true, SAML Response will be signed instead of SAML Assertion.
- nameIdentifierFormat (string): Default is urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified.
- nameIdentifierProbes (Array): Auth0 will try each of the attributes of this array in order. If one of them has a value, it will use that for the Subject/NameID. The order is: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier(mapped from user_id), http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress(mapped from email), http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name (mapped from name).
- authnContextClassRef (string): Default is urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified.
- typedAttributes (bool): Default is true. When set to true, we infer the xs:type of the element. Types are xs:string, xs:boolean, xs:doubleand xs:anyType. When set to false all xs:type are xs:anyType.
- includeAttributeNameFormat (bool): Default is true. When set to true, we infer the NameFormat based on the attribute name. NameFormat values are urn:oasis:names:tc:SAML:2.0:attrname-format:uri,urn:oasis:names:tc:SAML:2.0:attrname-format:basic and urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified. If set to false, the attribute NameFormat is not set in the assertion.
- logout (object): An object that controls SAML logout. It can contain two properties:callback (of type string), that contains the service provider (client application)'s Single Logout Service URL, where Auth0 will send logout requests and responses, and slo_enabled(boolean) that controls whether Auth0 should notify service providers of session termination. The default value istrue (notify service providers).
- binding (string): Optionally indicates the protocol binding used for SAML logout responses. By default Auth0 uses HTTP-POST, but you can switch to HTTP-Redirect by setting "binding" to "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect".
- signingCert (string): Optionally indicates the public key certificate used to validate SAML requests. If set, SAML requests will be required to be signed. A sample value would be "-----BEGIN PUBLIC KEY-----\nMIGf...bpP/t3\n+JGNGIRMj1hF1rnb6QIDAQAB\n-----END PUBLIC KEY-----\n".