Entra ID, Okta, AD FS로 구현하는 Delphi SAML 싱글 사인온

· 컴포넌트
Entra ID, Okta, AD FS로 구현하는 Delphi SAML 싱글 사인온

대형 고객이라면 언젠가 반드시 이런 질문을 합니다. 우리 직원들이 회사 계정으로 당신들의 애플리케이션에 로그인할 수 있나요? 이는 또 다른 사용자 이름과 비밀번호를 의미하는 것이 아닙니다. 그들이 이미 다른 모든 곳에서 사용하고 있는 Microsoft Entra ID, Okta 또는 AD FS 로그인을 의미하며, 여기에는 자체 비밀번호 정책, 자체 2단계 인증, 그리고 누군가 퇴사하는 날 계정을 한 곳에서 비활성화할 수 있는 체계가 포함됩니다.

고객의 ID 팀이 기대하는 답은 SAML 2.0입니다. 새로운 로그인 컴포넌트 개요에서 SAML은 한 단락만 다뤄졌습니다. 이 글은 전체 흐름을 다룹니다. TsgcSAMLServiceProvider가 하는 일, 로그인 페이지와 Assertion Consumer Service의 코드, 일반적인 identity provider에 애플리케이션을 등록하는 방법, 그리고 어디에도 계정 없이 오늘 바로 전부 테스트하는 방법까지 말입니다.

SAML 로그인의 작동 방식

세 주체가 참여합니다. 애플리케이션은 service provider(SP)입니다. 고객의 디렉터리는 identity provider(IdP)입니다. 브라우저가 둘 사이에서 메시지를 전달하므로, 서버와 IdP는 직접 통신하지 않습니다.

  1. 사용자가 로그인 URL을 엽니다. 애플리케이션은 AuthnRequest를 만들어 브라우저를 IdP로 리디렉션합니다.
  2. IdP는 회사가 정한 비밀번호, MFA, 조건부 액세스 규칙에 따라 사용자를 로그인시킵니다.
  3. IdP는 서명된 SAMLResponse로 응답하고, 브라우저는 이를 Assertion Consumer Service(ACS) URL로 POST합니다.
  4. 애플리케이션은 응답을 검증하고, 그 안에 지정된 사용자를 위해 자체 세션을 생성합니다.

네 번째 단계가 바로 SAML 구현이 잘못되기 쉬운 지점이며, 이 컴포넌트가 여러분을 대신해 처리해 주는 부분입니다.

Service Provider, 단계별로 살펴보기

TsgcSAMLServiceProvider는 HTTP 서버가 아닙니다. SAML 메시지를 구성하고 검사하는 역할을 하며, 애플리케이션이 이미 가지고 있는 서버의 요청 핸들러에서 이를 호출합니다. 예를 들어 TsgcWebSocketHTTPServerTsgcHTTPServer가 있습니다.

시작 시 EntityID, AssertionConsumerServiceURL, LoadIdPMetadata를 한 번 설정해 두면, 로그인 페이지와 ACS를 하나의 요청 핸들러 안에 담을 수 있습니다.

uses
  sgcAuth_SAML_SP;

procedure TMyApp.OnCommandGet(AContext: TIdContext;
  ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
  vRelayState, vRequestID: string;
  oResult: TsgcSAMLResult;
begin
  if ARequestInfo.Document = '/saml/login' then
  begin
    // 1. send the browser to the identity provider
    vRelayState := NewRelayState;
    AResponseInfo.Redirect(FSAML.GetAuthnRequestRedirectURL(vRelayState,
      vRequestID));
    // 2. keep the request id, the response must answer it
    AddPendingRequest(vRelayState, vRequestID);
  end
  else if (ARequestInfo.Document = '/saml/acs') and
    SameText(ARequestInfo.Command, 'POST') then
  begin
    // 3. the browser posts SAMLResponse and RelayState back
    vRelayState := ARequestInfo.Params.Values['RelayState'];
    vRequestID := TakePendingRequest(vRelayState);
    oResult := TsgcSAMLResult.Create;
    try
      if FSAML.ProcessResponse(ARequestInfo.Params.Values['SAMLResponse'],
        vRelayState, vRequestID, oResult) then
      begin
        // 4. signed in: create your own session for this user
        CreateUserSession(AResponseInfo, oResult.NameID, oResult.SessionIndex);
        AResponseInfo.Redirect('/');
      end
      else
        AResponseInfo.ResponseNo := 403; // log oResult.ErrorMessage
    finally
      oResult.Free;
    end;
  end;
end;

NewRelayState, AddPendingRequest, TakePendingRequest, CreateUserSession은 여러분 자신의 코드를 나타냅니다. 즉 GUID, RelayState를 키로 하여 각 요청 id를 한 번만 내어주는 thread safe 리스트, 그리고 애플리케이션의 세션 쿠키입니다. 속성은 Name=Value 형태의 줄로 도착하므로 oResult.Attributes.Values['email']로 이름을 지정해 하나를 읽을 수 있습니다. Entra ID는 http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress와 같은 claim URI로 속성 이름을 지정합니다.

RelayState는 IdP 서명의 보호 대상이 아닙니다. 이를 여러분 자신의 대기 중인 요청을 찾는 키로만 사용하고, 확인 없이 리디렉션하는 URL로는 절대 사용하지 마십시오.

Identity Provider에 애플리케이션 등록하기

GetMetadata는 service provider의 메타데이터를 반환합니다. 즉 entity ID와 HTTP-POST binding을 사용하는 ACS URL입니다. 이를 /saml/metadata와 같은 URL로 제공하거나 파일로 저장하여 IdP에 전달합니다. 모든 identity provider는 SP의 entity ID와 ACS URL이라는 동일한 두 값을 요구하므로, 아래 설명은 주로 각 콘솔이 이를 어디에 보관하는지에 관한 것입니다. 모든 경우에 어설션 암호화는 꺼둡니다.

어떤 IdP든 마지막 단계는 동일합니다. IdP의 메타데이터를 LoadIdPMetadata에 전달합니다. 문서가 여러 엔티티를 설명하는 경우, 두 번째 매개변수로 여러분의 것을 선택합니다.

ProcessResponse가 검사하는 것

SAML 응답은 서명된 XML 문서이며, 잘 알려진 SAML 취약점 대부분은 service provider가 실제로 서명된 것과 다른 내용을 읽도록 만드는 방법입니다. 응답은 다음 검사를 모두 통과해야만 수락됩니다.

파서는 또한 DOCTYPE 선언을 거부하므로 외부 엔티티가 존재하지 않으며, 문서의 크기와 중첩 깊이도 제한합니다. issuer는 여러분이 구성한 IdP여야 합니다. 첫 번째로 실패한 검사가 검증을 중단시키며, 그 이유는 ErrorMessage에 담깁니다. 이를 로그로 남기고 사용자에게는 단순한 “로그인 실패” 페이지를 보여주십시오.

계정 없이 시험해 보기

SAML이 작동하는 모습을 보기 위해 Entra ID 테넌트가 필요하지는 않습니다. Mock SAML은 mocksaml.com에서 제공하는 무료 테스트용 identity provider입니다. 어떤 service provider든 받아들이며, AuthnRequest에서 audience와 ACS URL을 그대로 가져오므로 등록할 것이 없습니다.

데모 Demos\26.Authentication\03.SAML_ServiceProviderTsgcWebSocketHTTPServer 위에 만들어진 완전한 service provider이며, http://localhost:8090에서 /login, /acs, /metadata 엔드포인트를 제공합니다.

  1. 데모를 빌드하고 libcrypto-3.dll과 libssl-3.dll을 실행 파일 옆에 둡니다. 이 파일들은 데모 폴더에 있으며, OpenSSL이 RSA 서명을 검증하는 데 사용합니다.
  2. Load IdP metadata를 클릭합니다. 기본 소스는 mocksaml.com의 메타데이터 URL입니다.
  3. Start를 클릭한 다음 Open Browser를 클릭하고, 로그인 링크를 따라갑니다.
  4. mocksaml.com에서 example.com 도메인의 아무 사용자 이름과 아무 비밀번호를 입력합니다.
  5. 브라우저가 ACS로 돌아오고, 페이지에 NameID, SessionIndex, 그리고 id, email, firstName, lastName 속성이 표시됩니다.

이것이 정상적으로 동작하면 http://localhost:8090/metadata를 열어 실제 IdP에 등록하고, 데모에서 IdP 메타데이터를 불러온 다음 다시 로그인해 보십시오. AD FS의 경우 AD FS가 https만 허용하므로 먼저 SSL로 데모를 실행하십시오.

현재의 제한 사항

문서

받는 방법

TsgcSAMLServiceProvider는 Delphi와 C++ Builder용 sgcWebSockets의 Enterprise 및 All-Access 에디션에 포함되어 있으며, 동일한 컴포넌트가 sgcWebSockets .NET에도 포함되어 있습니다. 인증 기능만 필요하다면 sgcAuth 패키지가 다른 로그인 컴포넌트와 함께 이를 제공합니다. 유닛 이름은 sgcAuth_SAML_SP이며, 컴포넌트를 폼에 올려놓기 전까지는 기존 애플리케이션에서 아무것도 바뀌지 않습니다.

다음 읽을거리

영상으로 보기

eSeGeCe 채널에 “SAML single sign-on in Delphi with Entra ID, Okta and AD FS”라는 제목의 짧은 영상이 있습니다. IDE에서의 코드와 mocksaml.com을 상대로 한 데모의 실시간 로그인을 보여줍니다.

질문이나 피드백이 있으신가요? identity provider 연결에 도움이 필요하신가요? 문의하기. 코드를 작성한 팀이 직접 답변해 드립니다.