Authentifizierung gegen Active Directory mit Java unter Linux


74

Ich habe eine einfache Aufgabe, mich mit Java bei Active Directory zu authentifizieren. Nur die Anmeldeinformationen überprüfen und sonst nichts. Angenommen, meine Domain ist "fun.xyz.tld", der Pfad der Organisationseinheit ist unbekannt und der Benutzername / das Passwort lautet testu / testp.

Ich weiß, dass es einige Java-Bibliotheken gibt, die diese Aufgabe vereinfachen, aber ich konnte sie nicht erfolgreich implementieren. Die meisten Beispiele, die ich gefunden habe, betrafen LDAP im Allgemeinen, nicht speziell Active Directory. Das Ausgeben einer LDAP-Anforderung bedeutet das Senden eines OU-Pfads, den ich nicht habe. Außerdem sollte die Anwendung, die eine LDAP-Anforderung ausgibt, bereits an Active Directory gebunden sein, um darauf zugreifen zu können ... Unsicher, da die Anmeldeinformationen an einem erkennbaren Ort gespeichert werden müssten. Ich möchte, wenn möglich, eine Testbindung mit Testanmeldeinformationen - dies würde bedeuten, dass das Konto gültig ist.

Gibt es, wenn möglich, eine Möglichkeit, einen solchen Authentifizierungsmechanismus verschlüsseln zu lassen? Ich weiß, dass AD Kerberos verwendet, bin mir aber nicht sicher, ob die LDAP-Methoden von Java dies tun.

Hat jemand ein Beispiel für Arbeitscode? Vielen Dank.

Antworten:


98

Es gibt drei Authentifizierungsprotokolle, mit denen die Authentifizierung zwischen Java und Active Directory unter Linux oder einer anderen Plattform durchgeführt werden kann (und die nicht nur für HTTP-Dienste spezifisch sind):

  1. Kerberos - Kerberos bietet Single Sign-On (SSO) und Delegierung, aber Webserver benötigen auch SPNEGO-Unterstützung, um SSO über den IE zu akzeptieren.

  2. NTLM - NTLM unterstützt SSO über den Internet Explorer (und andere Browser, wenn diese ordnungsgemäß konfiguriert sind).

  3. LDAP - Eine LDAP-Bindung kann verwendet werden, um einfach einen Kontonamen und ein Kennwort zu überprüfen.

Es gibt auch etwas namens "ADFS", das SSO für Websites mit SAML bereitstellt, die den Windows-SSP aufrufen. In der Praxis handelt es sich also im Grunde genommen um einen Umweg, eines der anderen oben genannten Protokolle zu verwenden.

Jedes Protokoll hat seine Vorteile, aber als Faustregel sollten Sie für maximale Kompatibilität im Allgemeinen versuchen, "so zu tun, wie Windows es tut". Was macht Windows?

Erstens begünstigt die Authentifizierung zwischen zwei Windows-Computern Kerberos, da Server nicht mit dem Domänencontroller kommunizieren müssen und Clients Kerberos-Tickets zwischenspeichern können, wodurch die Belastung der Domänencontroller verringert wird (und weil Kerberos die Delegierung unterstützt).

Wenn die authentifizierenden Parteien jedoch nicht beide über Domänenkonten verfügen oder der Client nicht mit dem DC kommunizieren kann, ist NTLM erforderlich. Kerberos und NTLM schließen sich also nicht gegenseitig aus und NTLM wird von Kerberos nicht überholt. In gewisser Hinsicht ist NTLM sogar besser als Kerberos. Beachten Sie, dass ich, wenn ich Kerberos und NTLM in einem Atemzug erwähne, auch SPENGO und die integrierte Windows-Authentifizierung (IWA) erwähnen muss. IWA ist ein einfacher Begriff, der im Grunde Kerberos oder NTLM oder SPNEGO bedeutet, um Kerberos oder NTLM auszuhandeln.

Die Verwendung einer LDAP-Bindung zum Überprüfen von Anmeldeinformationen ist nicht effizient und erfordert SSL. Bis vor kurzem war die Implementierung von Kerberos und NTLM jedoch schwierig, sodass die Verwendung von LDAP als Make-Shift-Authentifizierungsdienst weiterhin besteht. An dieser Stelle sollte dies jedoch generell vermieden werden. LDAP ist ein Informationsverzeichnis und kein Authentifizierungsdienst. Verwenden Sie es für den vorgesehenen Zweck.

Wie implementieren Sie Kerberos oder NTLM in Java und insbesondere im Kontext von Webanwendungen?

Es gibt eine Reihe großer Unternehmen wie Quest Software und Centrify, die Lösungen anbieten, die speziell Java erwähnen. Ich kann diese nicht wirklich kommentieren, da es sich um unternehmensweite "Identity Management-Lösungen" handelt. Wenn man sich also den Marketing-Spin auf ihrer Website ansieht, ist es schwierig, genau zu sagen, welche Protokolle wie verwendet werden. Sie müssten sie für die Details kontaktieren.

Die Implementierung von Kerberos in Java ist nicht besonders schwierig, da die Standard-Java-Bibliotheken Kerberos über die Klassen org.ietf.gssapi unterstützen. Bis vor kurzem gab es jedoch eine große Hürde: Der IE sendet keine rohen Kerberos-Token, sondern SPNEGO-Token. Aber mit Java 6 wurde SPNEGO implementiert. Theoretisch sollten Sie in der Lage sein, GSSAPI-Code zu schreiben, mit dem IE-Clients authentifiziert werden können. Aber ich habe es nicht versucht. Die Sun-Implementierung von Kerberos war im Laufe der Jahre eine Komödie von Fehlern. Aufgrund der Erfolgsbilanz von Sun in diesem Bereich würde ich keine Zusagen über die SPENGO-Implementierung machen, bis Sie diesen Vogel in der Hand haben.

Für NTLM gibt es ein kostenloses OSS-Projekt namens JCIFS mit einem Servlet-Filter für die NTLM-HTTP-Authentifizierung. Es wird jedoch eine Man-in-the-Middle-Methode verwendet, um die Anmeldeinformationen mit einem SMB-Server zu überprüfen, der nicht mit NTLMv2 funktioniert (was langsam zu einer erforderlichen Sicherheitsrichtlinie für Domänen wird). Aus diesem und anderen Gründen soll der HTTP-Filterteil von JCIFS entfernt werden. Beachten Sie, dass es eine Reihe von Ausgründungen gibt, die JCIFS verwenden, um dieselbe Technik zu implementieren. Wenn Sie also andere Projekte sehen, die behaupten, NTLM SSO zu unterstützen, überprüfen Sie das Kleingedruckte.

Die einzig richtige Möglichkeit, NTLM-Anmeldeinformationen mit Active Directory zu überprüfen, ist die Verwendung des DCERPC-Aufrufs NetrLogonSamLogon über NETLOGON mit Secure Channel. Gibt es so etwas in Java? Ja. Hier ist es:

http://www.ioplex.com/jespa.html

Jespa ist eine 100% Java NTLM-Implementierung, die NTLMv2, NTLMv1, vollständige Integritäts- und Vertraulichkeitsoptionen sowie die oben erwähnte Überprüfung der NETLOGON-Anmeldeinformationen unterstützt. Dazu gehören ein HTTP-SSO-Filter, ein JAAS-Anmeldemodul, ein HTTP-Client, ein SASL-Client und -Server (mit JNDI-Bindung), ein generischer "Sicherheitsanbieter" zum Erstellen benutzerdefinierter NTLM-Dienste und vieles mehr.

Mike


5
Update: HttpClient unterstützt jetzt die Authentifizierungsschemata Basic, Digest, NTLMv1, NTLMv2, NTLM2 Session, SNPNEGO und Kerberos.
Jonathan Barbero

51

Hier ist der Code, den ich anhand eines Beispiels aus diesem Blog zusammengestellt habe: LINK und diese Quelle: LINK .

import com.sun.jndi.ldap.LdapCtxFactory;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Iterator;
import javax.naming.Context;
import javax.naming.AuthenticationException;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import static javax.naming.directory.SearchControls.SUBTREE_SCOPE;

class App2 {

    public static void main(String[] args) {

        if (args.length != 4 && args.length != 2) {
            System.out.println("Purpose: authenticate user against Active Directory and list group membership.");
            System.out.println("Usage: App2 <username> <password> <domain> <server>");
            System.out.println("Short usage: App2 <username> <password>");
            System.out.println("(short usage assumes 'xyz.tld' as domain and 'abc' as server)");
            System.exit(1);
        }

        String domainName;
        String serverName;

        if (args.length == 4) {
            domainName = args[2];
            serverName = args[3];
        } else {
            domainName = "xyz.tld";
            serverName = "abc";
        }

        String username = args[0];
        String password = args[1];

        System.out
                .println("Authenticating " + username + "@" + domainName + " through " + serverName + "." + domainName);

        // bind by using the specified username/password
        Hashtable props = new Hashtable();
        String principalName = username + "@" + domainName;
        props.put(Context.SECURITY_PRINCIPAL, principalName);
        props.put(Context.SECURITY_CREDENTIALS, password);
        DirContext context;

        try {
            context = LdapCtxFactory.getLdapCtxInstance("ldap://" + serverName + "." + domainName + '/', props);
            System.out.println("Authentication succeeded!");

            // locate this user's record
            SearchControls controls = new SearchControls();
            controls.setSearchScope(SUBTREE_SCOPE);
            NamingEnumeration<SearchResult> renum = context.search(toDC(domainName),
                    "(& (userPrincipalName=" + principalName + ")(objectClass=user))", controls);
            if (!renum.hasMore()) {
                System.out.println("Cannot locate user information for " + username);
                System.exit(1);
            }
            SearchResult result = renum.next();

            List<String> groups = new ArrayList<String>();
            Attribute memberOf = result.getAttributes().get("memberOf");
            if (memberOf != null) {// null if this user belongs to no group at all
                for (int i = 0; i < memberOf.size(); i++) {
                    Attributes atts = context.getAttributes(memberOf.get(i).toString(), new String[] { "CN" });
                    Attribute att = atts.get("CN");
                    groups.add(att.get().toString());
                }
            }

            context.close();

            System.out.println();
            System.out.println("User belongs to: ");
            Iterator ig = groups.iterator();
            while (ig.hasNext()) {
                System.out.println("   " + ig.next());
            }

        } catch (AuthenticationException a) {
            System.out.println("Authentication failed: " + a);
            System.exit(1);
        } catch (NamingException e) {
            System.out.println("Failed to bind to LDAP / get account information: " + e);
            System.exit(1);
        }
    }

    private static String toDC(String domainName) {
        StringBuilder buf = new StringBuilder();
        for (String token : domainName.split("\\.")) {
            if (token.length() == 0)
                continue; // defensive check
            if (buf.length() > 0)
                buf.append(",");
            buf.append("DC=").append(token);
        }
        return buf.toString();
    }

}

4
import com.sun.jndi.ldap.LdapCtxFactory; - this will most likely only work with a Sun JVM.
Thorbjørn Ravn Andersen

6

I just finished a project that uses AD and Java. We used Spring ldapTemplate.

AD is LDAP compliant (almost), I don't think you will have any issues with the task you have. I mean the fact that it is AD or any other LDAP server it doesn't matter if you want just to connect.

I would take a look at: Spring LDAP

They have examples too.

As for encryption, we used SSL connection (so it was LDAPS). AD had to be configured on a SSL port/protocol.

But first of all, make sure you can properly connect to your AD via an LDAP IDE. I use Apache Directory Studio, it is really cool, and it is written in Java. That is all I needed. For testing purposes you could also install Apache Directory Server


Luchiani, I am currently develop web application to integration of java spring to share point(windows), mine while i am not able to create user in active directory using java code, can you share your code for create user in active directory with comment so that i can continue my work on time.
Janak Dhanani

5

As ioplex and others have said, there are many options. To authenticate using LDAP (and the Novell LDAP API), I have used something like:


LDAPConnection connection = new LDAPConnection( new LDAPJSSEStartTLSFactory() );
connection.connect(hostname, port);
connection.startTLS();
connection.bind(LDAPConnection.LDAP_V3, username+"@"+domain, password.getBytes());

As a "special feature", Active Directory allows LDAP binds against "user@domain" without using the distinguished name of the account. This code uses StartTLS to enable TLS encryption on the connection; the other alternative is LDAP over SSL, which is not supported by my AD servers.

The real trick is in locating the server and host; the official way is to use a DNS SRV (service) record lookup to locate a bundle of candidate hosts, then do a UDP-based LDAP "ping" (in a particular Microsoft format) to locate the correct server. If you are interested, I've posted some blog articles about my journey of adventure and discovery in that area.

If you want to do Kerberos-based username/password authentication, you are looking at another kettle of fish; it is doable with the Java GSS-API code, although I am not sure it performs the final step to validate the authentication. (The code doing the validation can contact the AD server to check the username and password, which results in a ticket granting ticket for the user, but to ensure the AD server is not being impersonated, it also needs to try to get a ticket for the user to itself, which is somewhat more complicated.)

If you want to do Kerberos-based single sign-on, assuming your users are authenticated to the domain, you can do that as well with the Java GSS-API code. I would post a code sample, but I still need to turn my hideous prototype into something fit for human eyes. Check out some code from SpringSource for some inspiration.

If you are looking for NTLM (which I was given to understand is less secure) or something else, well, good luck.


Very useful blog entries. Thank you!
Andrey Rodionov

3

Are you just verifying credentials? In that case you could just do plain kerberos and not bother with LDAP.


Yes, only verifying credentials. I edited the question with clarification. Is the code any different from LDAP auth?
DV.




0

I recommend you to look at the adbroker package of the oVirt project. It uses Spring-Ldap and the Krb5 JAAS Login module (with GSSAPI) in order to authenticate using Kerberos against Ldap servers (Active-Directory, ipa, rhds, Tivoli-DS). Look for the code at engine\backend\manager\modules\bll\src\main\java\org\ovirt\engine\core\bll\adbroker

You can use git to clone the repository or browse using the gerrit link

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.