SingleSignOn (SSO) via NT LAN Mananger (NTLM) am DeliveryServer - Projekt - einfach oder schwer? Wie kann das nicht mehr unterstützte Modul "A005" ersetzt/abgelöst werden?
Eine automatische Anmeldung ist schon fast ein "Quasi-Standard" in jedem Intranet-Projekt, die Realisierung birgt aber meist kleinere oder größere Hürden (z.B technische Voraussetzungen, Projektanforderungen), die überwunden werden müssen.
Mit dieser Artikel möchte ich einen Einstieg in das Thema "SSO via NTLM" geben und erste Lösungsansätze aufzeigen.
NTLM über HTTP ist, wie auch "Basic Authentification" und "Digest Access Authentication", ein HTTP-Authentifizierungs-Verfahren. Als Zugangsdaten (Credentials) werden die Windows-Benutzeranmeldung verwendet und verschlüsselt im Header übertragen.
Im Groben kann der Ablauf einer NTLM-Authentifizierung wie folgt beschreiben werden:
Um Schritt 3.2 ausführen zu können, benötigt der Server lesenden Zugriff auf die Passwortfelder der Nutzer im Benutzer-Repository. Da dies nicht "Mal eben so" programmiert ist, werde ich im Folgenden nur die Anmeldung über den Nutzernamen ohne Passwortprüfung für die OTWSM Delivery Server Anbindung aufzeigen.
Das automatische Senden des Benutzernamens bei einer NTLM - Authentifizierung kann im Browser konfiguriert werden. Im "Internet Explorer" ist dies für die Zone "Intranet" in den Standardeinstellungen bereits aktiviert und diese Einstellungen werden auch vom "Chrome" verwendet. Im "Firefox" hingegen muss das automatische Senden einer NTLM-Kennung aktiviert werden, da es per Standardeinstellung deaktiviert ist.
In der Regel werden diese Browsereinstellungen in Firmennetzwerken (z.B. über Gruppenrichtlinien) vorgegeben.
Im Firefox sind die Konfigurationseinstellungen durch Eingabe von "about:config" aufrufen. Die Einstellungen zu NTLM finden sich unter "network.automatic-ntlm-auth.*". Unter "trusted-uris" können kommasepariert alle ULR's angegeben werden, für die eine NTLM-Kennung gesendet werden soll. Mit der Option "allow-non-fgdn" ist es nicht zwingend notwenig, den Domainnamen voll qualifizierten anzugeben, dies ist recht nützlich um Subdomains abzubilden, ohne diese explizit unter "trusted-uris" aufzuführen.
Die oben aufgeführten Einstellungen gelten für "NTLM über HTTP" im Allgemeinen und nicht speziell für den Delivery Server.
In diesem Abschnitt werde ich eine "automatische Anmeldung" an einem OTWSM DeliveryServer im Modus "trusted" (ohne Kennwortprüfung) aufzeigen.
Wie bereits beschrieben sendet der Firefox NTLM nicht in gleicher Form wie IE und Chrome, daher die erste Prüfung ob die Anfrage den HTTP-Header "authorization" enthält. Ist dies nicht der Fall wird der HTTP-Statuscode: 401 und die Authentifizierungsmethode: NTLM vom OTWSM DS an den Browser gesendet. Die bestehende HTTP-Verbindung soll für die nächsten Schritte erhalten bleiben (connection:keep-alive).
<rde-dm:attribute mode="condition"> <rde-dm:constraint>(request:rde-rd:httpHeader-authorization eq '')</rde-dm:constraint> <rde-dm:if> <rde-dm:attribute mode="write" attribute="request:rdeResponseStatus" op="set" value="401" /> <rde-dm:attribute mode="write" attribute="response:WWW-Authenticate" op="add" value="NTLM" /> <rde-dm:attribute mode="write" attribute="response:connection" op="add" value="keep-alive" /> </rde-dm:if> <rde-dm:else> <!-- NTLM - header is send --> <p>HTTP-Header: authorization is send</p> </rde-dm:else> </rde-dm:attribute>
Das Ergebnis kann im Netzwerkverkehr geprüft werden.
Der Firefox, sendet NTLM erst auf "Aufforderung".
Grob umschrieben kann man sagen, es existierten drei verschiedene NTLM-Messagetypen welche sich im Wesentlichen nur im Umfang und Codierung der enthaltenen Daten unterscheiden.
Für mein Beispiel sind nur der Typ 1 (wird vom Browser automatisch gesendet) und Typ 3 (enthält alle relevanten Daten) von Bedeutung, dies gilt aber auch für den größten Teil der mir bekannten NTLM-Verfahren.
Zur Auswertung der Typen werden die beiden selbstgeschriebenen Inline-Funktionen von mir genutzt.
package org.owug.otwsm.inlinefunction; import org.apache.commons.codec.binary.Base64; public class NtlmHelper { public String ntlmTypeOne(String auth) { String result = "1"; byte msg[] = Base64.decodeBase64(auth.substring(5)); if(msg[8] == 1) { byte z = 0; byte msg1[] = { 78, 84, 76, 77, 83, 83, 80, z, 2, z, z, z, z, z, z, z, 40, z, z, z, 1, -126, z, z, z, 2, 2, 2, z, z, z, z, z, z, z, z, z, z, z, z }; result = Base64.encodeBase64String(msg1); return result; } else { return "Error NTLM-Typ 1"; } } public String ntlmTypeThree(String auth) { String result = "3"; int off = 0; byte msg[] = Base64.decodeBase64(auth.substring(5)); if(msg[8] == 3) { off = 30; int length = msg[off + 17] * 256 + msg[off + 16]; int offset = msg[off + 19] * 256 + msg[off + 8]; result = new String(msg, offset, length); length = msg[off + 1] * 256 + msg[off]; offset = msg[off + 3] * 256 + msg[off + 2]; result = new String(msg, offset, length); length = msg[off + 9] * 256 + msg[off + 8]; offset = msg[off + 11] * 256 + msg[off + 10]; result = new String(msg, offset, length); return result; } else { return "Error NTLM-Typ 3"; } } }
Unter Nutzung der Inline-Funktionen kann der oben angeführte Code wie folgt ergänzt werden.
<rde-dm:else> <!-- NTLM - header is send --> <rde-dm:attribute mode="condition"> <rde-dm:constraint>[#request:rde-rd:httpHeader-authorization#].startsWith('NTLM ').toString() EQ 'true'</rde-dm:constraint> <rde-dm:if> <rde-dm:attribute mode="write" attribute="request:ntlmmsg1" op="set" value="[#request:rde-rd:httpHeader-authorization#].ntlmTypeOne()"/> <rde-dm:attribute mode="condition"> <rde-dm:constraint>[#request:ntlmmsg1#].startsWith('Error').toString() EQ 'false'</rde-dm:constraint> <rde-dm:if> <rde-dm:attribute mode="write" attribute="request:rdeResponseStatus" op="set" value="401" /> <rde-dm:attribute mode="write" attribute="response:WWW-Authenticate" op="add" value="NTLM [#request:ntlmmsg1#]" /> <rde-dm:attribute mode="write" attribute="response:connection" op="add" value="keep-alive" /> </rde-dm:if> <rde-dm:else> <rde-dm:attribute mode="write" attribute="request:ntlmmsg3" op="set" value="[#request:rde-rd:httpHeader-authorization#].ntlmTypeThree()"/> <rde-dm:attribute mode="condition"> <rde-dm:constraint>[#request:ntlmmsg3#].startsWith('Error').toString() EQ 'false'</rde-dm:constraint> <rde-dm:if> <rde-dm:attribute mode="write" attribute="session:auth_type" op="set" value="NTLM-Auth"/> <rde-dm:attribute mode="write" attribute="session:adusername" op="set" value="[#request:ntlmmsg3#].regexReplace('[_[^\\w\\däüöÄÜÖ\\+\\- ]]', '').trim().toUpperCase()"/> <!-- Do trusted Login --> <p>SSO UserName: <rde-dm:attribute mode="read" attribute="session:adusername" tag="notag"/></p> </rde-dm:if> <rde-dm:else> <!-- Redirect Errorpage --> </rde-dm:else> </rde-dm:attribute> </rde-dm:else> </rde-dm:attribute> </rde-dm:if> <rde-dm:else> <!-- Redirect Errorpage --> </rde-dm:else> </rde-dm:attribute> </rde-dm:else>
Das gerenderte Ergebnis sollte folgt aussehen:
Der letzte Schritt ist eher ein kleiner Hopser: Mit dem User-Dynament ein "trusted login" ausführen.
<!-- Do trusted Login -->
<rde-dm:user mode="login-trusted" user="[#session:adusername#]"
directory-service="[##DefaultDirectoryService#]"
login-content="login_successful.htm"
login-fail="login_failed.htm" />
SingleSignOn ist in den meisten Fällen nicht "mal eben so" implementiert. Vertraut man der "Windows-Anmeldung" ohne eine explizite Kennwort-Prüfung durchzuführen, kann der im Artikel beschriebene Weg genutzt werden.
Gibt es hingegen Vorgaben zur Security oder ist eine Abmeldung und die Anmeldung unter einer anderen Kennung notwendig, wird die Implementierung schon wieder aufwendiger.
Ich möchte an dieser Stelle auch die Frage aufwerfen, ob die Applikation "OTWSM - DeliveryServer" der "richtige" Ort für die NTLM-Authentifizierung ist, da die Anfrage vom Browser über den Webserver (IIS, Apache Webserver), die Servlet-Engine (z.B. Tomcat) zum OTWSM DS bereits zwei potentiell bessere Knoten durchlaufen hat.
Kommen weitere Systeme (z.B ProxyServer, PolicyAgent) zum Einsatz wird aus dem "kleinen" Thema "NTLM / SSO" ein Komplexes.
Holm Gehre ist seit Mitte 2017 Senior Softwareentwickler und Projektleiter bei CHEFS CULINAR und betreut dort mit seinem Team die Opentext-Plattform. Seit dem Jahr 2001 und bis Mitte des Jahres 2017 betreute und entwickelte er Opentext- (vormals RedDot-) basierte Webseiten nationaler und internationaler Kunden auf Basis von Management und Delivery Server.