Prędzej czy później duży klient zadaje pytanie: czy nasi pracownicy mogą logować się do waszej aplikacji za pomocą konta firmowego? Nie chodzi im o kolejną nazwę użytkownika i hasło. Chodzi im o logowanie Microsoft Entra ID, Okta lub AD FS, którego już używają do wszystkiego innego, z własną polityką haseł, własnym drugim składnikiem uwierzytelniania i jednym miejscem, w którym można wyłączyć konto w dniu odejścia pracownika.
Odpowiedzią, jakiej oczekuje ich zespół ds. tożsamości, jest SAML 2.0. W przeglądzie nowych komponentów logowania SAML zajął jeden akapit. Ten wpis to pełny przepływ: co robi TsgcSAMLServiceProvider, kod strony logowania i Assertion Consumer Service, jak zarejestrować aplikację u popularnych dostawców tożsamości oraz jak to wszystko przetestować już dziś bez konta gdziekolwiek.
Jak działa logowanie SAML
Biorą w nim udział trzy strony. Twoja aplikacja jest service providerem (SP). Katalog klienta jest identity providerem (IdP). Przeglądarka przenosi komunikaty między nimi, więc twój serwer i IdP nigdy nie rozmawiają ze sobą bezpośrednio.
- Użytkownik otwiera twój URL logowania. Twoja aplikacja buduje AuthnRequest i przekierowuje przeglądarkę do IdP.
- IdP loguje użytkownika, zgodnie z regułami haseł, MFA lub dostępu warunkowego ustalonymi przez firmę.
- IdP odpowiada podpisanym SAMLResponse, a przeglądarka wysyła je metodą POST do URL twojego Assertion Consumer Service (ACS).
- Twoja aplikacja waliduje odpowiedź i tworzy własną sesję dla użytkownika, którego ta odpowiedź wskazuje.
Krok czwarty to miejsce, w którym implementacje SAML popełniają błędy, i jest to dokładnie ta część, którą komponent wykonuje za ciebie.
Service provider, krok po kroku
TsgcSAMLServiceProvider nie jest serwerem HTTP. Buduje i sprawdza komunikaty SAML, a ty wywołujesz go z handlera żądań serwera, który twoja aplikacja już posiada, na przykład TsgcWebSocketHTTPServer lub TsgcHTTPServer.
- Opisz swoją aplikację. Ustaw
EntityID, unikalną nazwę twojej aplikacji (zwykle jej URL metadanych), orazAssertionConsumerServiceURL, URL https, pod którym dociera odpowiedź. - Opisz dostawcę tożsamości. Wywołaj
LoadIdPMetadataz dokumentem metadanych IdP. Odczytuje on entity ID IdP, jego URL logowania i binding, a także wszystkie certyfikaty podpisujące. Bez metadanych ustaw ręcznieIdPEntityID,IdPSSOURLiIdPCertificates. - Wyślij żądanie.
GetAuthnRequestRedirectURLzwraca URL, do którego należy przekierować przeglądarkę. Dla IdP oferującego tylko binding HTTP-POST,GetAuthnRequestPostFormzwraca zamiast tego stronę, która wysyła żądanie metodą POST. - Zachowaj id żądania. Obie metody zwracają id nowego AuthnRequest. Przechowaj je na serwerze, indeksowane losowym RelayState lub ciasteczkiem sesji, i usuń, gdy nadejdzie odpowiedź, tak aby każde żądanie mogło otrzymać odpowiedź tylko raz.
- Przetwórz odpowiedź. Pod URL ACS wywołaj
ProcessResponsez przesłanym SAMLResponse, RelayState i zapisanym id żądania. Gdy zwracaTrue,TsgcSAMLResultzawieraNameID,SessionIndexi każdy atrybut wysłany przez IdP. Gdy zwracaFalse,ErrorMessagewyjaśnia przyczynę, aOnSAMLErrorsię uruchamia.
Gdy EntityID, AssertionConsumerServiceURL i LoadIdPMetadata są ustawione raz przy starcie, strona logowania i ACS mieszczą się w jednym handlerze żądań:
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 i CreateUserSession reprezentują twój własny kod: GUID, thread safe listę indeksowaną RelayState, która wydaje każde id żądania tylko raz, oraz ciasteczko sesji twojej aplikacji. Atrybuty przychodzą jako linie Name=Value, więc oResult.Attributes.Values['email'] odczytuje jeden po nazwie. Entra ID nazywa je claim URI, takimi jak http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress.
RelayState nie jest objęty podpisem IdP. Używaj go jako klucza do odnalezienia własnego oczekującego żądania, nigdy jako URL, do którego przekierowujesz bez sprawdzenia.
Rejestracja aplikacji u dostawcy tożsamości
GetMetadata zwraca metadane service providera: twój entity ID i twój URL ACS z bindingiem HTTP-POST. Udostępnij je pod URL takim jak /saml/metadata, lub zapisz je w pliku, i przekaż IdP. Każdy dostawca tożsamości prosi o te same dwie wartości, entity ID SP i URL ACS, więc poniższe notatki dotyczą głównie tego, gdzie każda konsola je przechowuje. We wszystkich pozostaw wyłączone szyfrowanie asercji.
- Microsoft Entra ID. Enterprise applications, New application, Create your own application (non-gallery). W Single sign-on wybierz SAML, a następnie prześlij metadane SP lub wypełnij Identifier (Entity ID) i Reply URL. Przypisz użytkowników lub grupy i wczytaj App Federation Metadata Url pokazany w SAML Certificates.
- Okta. Applications, Create App Integration, SAML 2.0. Single sign-on URL to twój URL ACS, z zaznaczonym “Use this for Recipient URL and Destination URL”, a Audience URI to twój entity ID. Dodaj attribute statements takie jak email, firstName i lastName, przypisz osoby lub grupy i wczytaj Metadata URL z karty Sign On.
- AD FS. Dodaj claims aware Relying Party Trust i zaimportuj metadane SP. AD FS akceptuje wyłącznie endpointy https. Dodaj claim rules, które wysyłają Name ID, na przykład E-Mail-Addresses wysłane jako E-Mail Address, a następnie E-Mail Address przekształcone w Name ID. Metadane IdP znajdują się pod adresem
https://<adfs-host>/FederationMetadata/2007-06/FederationMetadata.xml. - Google Workspace. Admin console, Apps, Web and mobile apps, Add custom SAML app. Pobierz metadane IdP, wprowadź swój URL ACS i entity ID, wybierz Name ID (na przykład podstawowy adres e-mail) i włącz aplikację dla swoich użytkowników.
- Keycloak. Utwórz SAML client, którego Client ID jest twoim entity ID, lub zaimportuj metadane SP. Keycloak domyślnie podpisuje cały dokument, więc włącz również Sign assertions. Jeśli Client signature required jest włączone, ustaw
SignAuthnRequests,SPCertificateiSPPrivateKey. Metadane IdP znajdują się pod adresemhttps://<host>/realms/<realm>/protocol/saml/descriptor.
Niezależnie od IdP, ostatni krok jest taki sam: przekaż jego metadane do LoadIdPMetadata. Gdy dokument opisuje kilka jednostek, drugi parametr wybiera twoją.
Co sprawdza ProcessResponse
Odpowiedź SAML jest podpisanym dokumentem XML, a większość znanych podatności SAML to sposoby na to, by service provider odczytał coś innego niż to, co zostało podpisane. Odpowiedź jest akceptowana tylko wtedy, gdy przejdą wszystkie poniższe kontrole:
- Podpis, względem certyfikatu IdP. Odpowiedź jest weryfikowana wyłącznie certyfikatami z
IdPCertificates. Certyfikat osadzony w wiadomości nigdy nie jest zaufany, ponieważ atakujący również może taki osadzić. PrzyWantAssertionsSigned, wartości domyślnej, asercja musi nosić własny podpis. - Ochrona przed signature wrapping. Podpis musi wskazywać na element, którego ID jest unikatowe w dokumencie, a po weryfikacji odczytywany jest tylko podpisany element. Niepodpisana asercja podrzucona obok podpisanej nigdy nie jest brana pod uwagę.
- Jedna asercja. Odpowiedź musi zawierać dokładnie jedną asercję, bezpośrednio pod response.
- Audience i recipient. Audience musi być twoim
EntityID, a recipient twoimAssertionConsumerServiceURL, dzięki czemu asercja wystawiona dla innej aplikacji jest odrzucana. - Okno czasowe. NotBefore i NotOnOrAfter są sprawdzane względem UTC z tolerancją
ClockSkewsekund, domyślnie dwóch minut.MaxAssertionAgemoże także ograniczać maksymalny wiek asercji. - InResponseTo. Odpowiedź musi odpowiadać na id żądania, które zapisałeś. Niezamówione odpowiedzi zainicjowane przez IdP są odrzucane, chyba że ustawisz
AllowIdPInitiated. - Pamięć podręczna przeciw powtórkom. ID każdej zaakceptowanej asercji jest przechowywane aż do wygaśnięcia, więc ta sama odpowiedź wysłana dwukrotnie jest odrzucana. Pamięć podręczna jest thread safe i znajduje się w pamięci. Gdy kilka serwerów dzieli logowanie, nadpisz
DoAddToReplayCache, aby przechowywać ID we wspólnym magazynie. - SHA-1 domyślnie wyłączone. Podpisy RSA-SHA1 i skróty SHA-1 są odrzucane, chyba że ustawisz
AllowSHA1dla IdP, który wciąż ich potrzebuje.
Parser odrzuca również deklaracje DOCTYPE, więc nie ma zewnętrznych encji, i ogranicza rozmiar oraz głębokość zagnieżdżenia dokumentu. Issuer musi być IdP, który skonfigurowałeś. Pierwsza nieudana kontrola zatrzymuje walidację, a jej powód znajduje się w ErrorMessage: zapisz go w logu i pokaż użytkownikowi prostą stronę “logowanie nieudane”.
Wypróbuj bez konta
Nie potrzebujesz tenanta Entra ID, żeby zobaczyć SAML w działaniu. Mock SAML to darmowy testowy identity provider pod adresem mocksaml.com. Akceptuje dowolnego service providera i pobiera audience oraz URL ACS z AuthnRequest, więc nie ma nic do rejestrowania.
Demo Demos\26.Authentication\03.SAML_ServiceProvider to kompletny service provider na TsgcWebSocketHTTPServer, z endpointami /login, /acs i /metadata pod http://localhost:8090:
- Skompiluj demo i trzymaj libcrypto-3.dll oraz libssl-3.dll obok pliku wykonywalnego. Znajdują się w folderze demo, a OpenSSL weryfikuje podpisy RSA.
- Kliknij Load IdP metadata. Domyślnym źródłem jest URL metadanych mocksaml.com.
- Kliknij Start, potem Open Browser, i podążaj za linkiem logowania.
- Na mocksaml.com wpisz dowolną nazwę użytkownika z domeny example.com i dowolne hasło.
- Przeglądarka wraca do ACS, a strona pokazuje NameID, SessionIndex oraz atrybuty id, email, firstName i lastName.
Gdy to zadziała, otwórz http://localhost:8090/metadata, zarejestruj go u swojego prawdziwego IdP, wczytaj metadane IdP w demo i zaloguj się ponownie. W przypadku AD FS uruchom najpierw demo z SSL, ponieważ AD FS akceptuje wyłącznie https.
Obecne ograniczenia
- Brak szyfrowanych asercji. Odpowiedź z EncryptedAssertion lub zaszyfrowanym NameID jest odrzucana. Pozostaw szyfrowanie asercji wyłączone w IdP. Asercja nadal jest podpisana i przesyłana przez https.
- Brak Single Logout. SLO nie jest zaimplementowane. Zwracany jest
SessionIndex, aby twoja aplikacja mogła zakończyć własną sesję i zbudować własne wylogowanie.
Dokumentacja
Gdzie go zdobyć
TsgcSAMLServiceProvider jest zawarty w edycjach Enterprise i All-Access sgcWebSockets, dla Delphi i C++ Builder, a ten sam komponent jest częścią sgcWebSockets .NET. Jeśli potrzebujesz wyłącznie uwierzytelniania, pakiet sgcAuth zawiera go razem z pozostałymi komponentami logowania. Jednostką jest sgcAuth_SAML_SP, a w istniejącej aplikacji nic się nie zmienia, dopóki nie umieścisz komponentu na formularzu.
Czytaj dalej
- Logowanie w Delphi z Passkeys, SAML SSO, LDAP i TOTP 2FA
- PKCE OAuth2 w Delphi
- Autoryzacja przy użyciu PassKeys
Zobacz wideo
Na kanale eSeGeCe dostępne jest krótkie wideo, “SAML single sign-on in Delphi with Entra ID, Okta and AD FS”. Pokazuje kod w IDE oraz logowanie na żywo z użyciem demo względem mocksaml.com.
Masz pytania, uwagi lub potrzebujesz pomocy w podłączeniu swojego dostawcy tożsamości? Skontaktuj się z nami. Otrzymasz odpowiedź od osób, które napisały ten kod.
