Аутентификация Active Directory с помощью Java на Linux

У меня есть простая задача аутентификации Active Directory с помощью Java. Просто проверка учетных данных и ничего больше. Скажем, мой домен "fun.xyz.tld", путь OU неизвестен, а имя пользователя/пароль - testu/testp.

Я знаю там несколько библиотек Java, которые упрощают эту задачу, но мне не удалось их реализовать. Большинство примеров, которые я нашел, адресовали LDAP в целом, а не Active Directory. Выдача запроса LDAP означает отправку в него пути OU, которого у меня нет. Кроме того, приложение, которое выдает запрос LDAP, должно быть привязано к Active Directory, чтобы получить доступ к нему... Небезопасно, так как учетные данные должны быть сохранены в каком-либо месте для обнаружения. Я бы хотел, чтобы тестовое связывание с тестовыми учетными данными, если возможно, означало бы, что учетная запись действительна.

Последнее, если возможно, есть способ сделать такой механизм аутентификации зашифрованным? Я знаю, что AD использует Kerberos, но не уверен, работают ли методы LDAP Java.

Есть ли у кого-нибудь пример рабочего кода? Спасибо.

Ответ 1

Вот код, который я собрал на примере из этого блога: 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;

//import org.acegisecurity.AuthenticationException;
import org.acegisecurity.BadCredentialsException;
import org.acegisecurity.GrantedAuthority;
import org.acegisecurity.GrantedAuthorityImpl;
import org.acegisecurity.providers.AuthenticationProvider;
import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
import org.acegisecurity.providers.dao.AbstractUserDetailsAuthenticationProvider;
import org.acegisecurity.userdetails.UserDetails;
import org.acegisecurity.userdetails.UserDetailsService;
import org.acegisecurity.userdetails.UsernameNotFoundException;

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 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<GrantedAuthority> groups = new ArrayList<GrantedAuthority>();
            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(new GrantedAuthorityImpl(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().toString());
            }

        } 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();
    }

}

Ответ 2

Существует три протокола аутентификации, которые могут использоваться для аутентификации между Java и Active Directory в Linux или на любой другой платформе (и они не только специфичны для служб HTTP):

  • Kerberos - Kerberos обеспечивает Single Sign-On (SSO) и делегирование, но веб-серверы также нуждаются в поддержке SPNEGO для принятия SSO через IE.

  • NTLM - NTLM поддерживает SSO через IE (и другие браузеры, если они правильно настроены).

  • LDAP - привязка LDAP может использоваться для простой проверки имени учетной записи и пароля.

Также есть что-то, называемое "ADFS", которое предоставляет SSO для сайтов, использующих SAML, которые вызывают в SSP Windows, поэтому на практике это в основном окольный способ использования одного из других вышеперечисленных протоколов.

У каждого протокола есть свои преимущества, но, как правило, для максимальной совместимости вы обычно пытаетесь "делать то, что делает Windows". Итак, что делает Windows?

Во-первых, аутентификация между двумя машинами Windows благоприятствует Kerberos, потому что серверам не требуется связываться с DC, и клиенты могут кэшировать билеты Kerberos, что снижает нагрузку на DC (и потому, что Kerberos поддерживает делегирование).

Но если у аутентифицирующих сторон не есть учетные записи домена или если клиент не может связаться с DC, требуется NTLM. Таким образом, Kerberos и NTLM не являются взаимоисключающими, а NTLM не устаревает Kerberos. Фактически в некотором смысле NTLM лучше, чем Kerberos. Обратите внимание, что при упоминании Kerberos и NTLM на одном дыхании я должен также упомянуть SPENGO и Integrated Windows Authentication (IWA). IWA - простой термин, который в основном означает Kerberos или NTLM или SPNEGO для согласования Kerberos или NTLM.

Использование LDAP-связывания в качестве способа проверки учетных данных неэффективно и требует SSL. Но до недавнего времени внедрение Kerberos и NTLM было затруднительным, поэтому использование LDAP в качестве службы проверки подлинности смены-сдвига сохранилось. Но в этот момент его вообще следует избегать. LDAP - это каталог информации, а не служба проверки подлинности. Используйте его по назначению.

Итак, как вы реализуете Kerberos или NTLM в Java и в контексте веб-приложений в частности?

Существует множество крупных компаний, таких как Quest Software и Centrify, которые имеют решения, специально упоминающие Java. Я не могу прокомментировать их, поскольку они являются "решениями управления идентификацией" для всей компании, поэтому, глядя на маркетинг на своем веб-сайте, трудно точно сказать, какие протоколы используются и как. Вам нужно будет связаться с ними для получения подробной информации.

Реализация Kerberos в Java не является ужасно трудной, поскольку стандартные библиотеки Java поддерживают Kerberos через классы org.ietf.gssapi. Однако до недавнего времени существовало серьезное препятствие - IE не отправляет сырые маркеры Kerberos, он отправляет токены SPNEGO. Но с Java 6 SPNEGO был реализован. Теоретически вы должны написать код GSSAPI, который может аутентифицировать IE-клиенты. Но я не пробовал. Реализация Sun Kerberos была комедией ошибок на протяжении многих лет, поэтому на основе записи Sun в этой области я бы не сделал никаких promises об их реализации SPENGO, пока у вас не будет этой птицы.

Для NTLM существует бесплатный проект OSS под названием JCIFS, в котором установлен сервлет-фильтр HTTP-аутентификации NTLM. Однако он использует метод "человек-в-середине" для проверки учетных данных с SMB-сервером, который не работает с NTLMv2 (который постепенно становится необходимой политикой безопасности домена). По этой причине и другие, часть HTTP-фильтра JCIFS планируется удалить. Обратите внимание, что существует ряд побочных эффектов, которые используют JCIFS для реализации той же методики. Поэтому, если вы видите другие проекты, которые заявляют о поддержке SSO NTLM, проверьте мелкий шрифт.

Единственный правильный способ проверки учетных данных NTLM с Active Directory - это вызов NetRLogonSamLogon DCERPC через NETLOGON с Secure Channel. Существует ли такая вещь на Java? Да. Вот он:

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

Jespa - это 100% реализация Java NTLM, поддерживающая NTLMv2, NTLMv1, полные параметры целостности и конфиденциальности и вышеупомянутую проверку подлинности NETLOGON. И он включает в себя HTTP SSO Filter, JAAS LoginModule, HTTP-клиент, клиент и сервер SASL (с привязкой JNDI), общий "поставщик безопасности" для создания пользовательских служб NTLM и т.д.

Mike

Ответ 3

Я только что закончил проект, который использует AD и Java. Мы использовали Spring ldapTemplate.

AD является совместимым с LDAP (почти), я не думаю, что у вас возникнут проблемы с вашей задачей. Я имею в виду тот факт, что это AD или любой другой сервер LDAP, не имеет значения, хотите ли вы просто подключиться.

Я бы посмотрел: Spring LDAP

У них также есть примеры.

Что касается шифрования, мы использовали SSL-соединение (так было LDAPS). AD должен был быть настроен на порт/протокол SSL.

Но прежде всего, убедитесь, что вы правильно подключаетесь к AD через LDAP IDE. Я использую Apache Directory Studio, это действительно классно, и он написан на Java. Это все, что мне нужно. Для целей тестирования вы также можете установить Apache Directory Server

Ответ 4

Вы просто проверяете учетные данные? В этом случае вы можете просто сделать простые кеберосы и не беспокоиться о LDAP.

Ответ 5

Как сказал ioplex и другие, есть много вариантов. Для аутентификации с использованием LDAP (и API Novell LDAP) я использовал что-то вроде:


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

Как "специальная функция", Active Directory позволяет привязывать LDAP к "user @domain" без использования отличительного имени учетной записи. Этот код использует StartTLS для включения шифрования TLS в соединении; другой альтернативой является LDAP через SSL, который не поддерживается моими серверами AD.

Реальный трюк заключается в поиске сервера и хоста; официальным способом является использование поиска DNS SRV (службы) для поиска пакета узлов-кандидатов, а затем выполнить "ping" LDAP на основе UDP (в определенном формате Microsoft), чтобы найти правильный сервер. Если вам интересно, я опубликовал некоторые статьи в блоге о моем путешествии приключений и открытий в этой области.

Если вы хотите использовать аутентификацию имени пользователя и пароля на основе Kerberos, вы смотрите на другой чайник рыбы; он выполним с кодом Java GSS-API, хотя я не уверен, что он выполняет последний шаг для проверки подлинности. (Код, выполняющий проверку, может связываться с сервером AD для проверки имени пользователя и пароля, что приводит к предоставлению билета для пользователя, но для обеспечения того, чтобы сервер AD не выдавал себя за образец, ему также необходимо попытаться получить билет для пользователь к себе, что несколько сложнее.)

Если вы хотите сделать единую регистрацию на основе Kerberos, если ваши пользователи прошли аутентификацию в домене, вы можете сделать это также с кодом Java GSS-API. Я бы опубликовал образец кода, но мне все же нужно превратить свой отвратительный прототип в нечто подходящее для глаз человека. Проверьте некоторый код из SpringSource для некоторого вдохновения.

Если вы ищете NTLM (который мне дал понять, он менее безопасен) или что-то еще, ну, удачи.

Ответ 6

Если все, что вы хотите сделать, является аутентификацией против AD, использующей Kerberos, тогда простую программу http://spnego.sourceforge.net/HelloKDC.java должна сделать это.

Взгляните на документацию по проекту "перед полетом", в которой рассказывается о программе HelloKDC.java.

Ответ 8

ldap аутентификация без SSL небезопасна, и каждый может просматривать учетные данные пользователя, поскольку ldap-клиент переносит usernamae и пароль во время операции привязки ldap. Поэтому всегда используйте протокол ldaps. источник: Аутентификация Ldap Active Directory в Java Spring Безопасность с примером

Ответ 9

Я рекомендую вам посмотреть пакет adbroker проекта oVirt. Он использует Spring -Ldap и модуль входа JAAS Krb5 (с GSSAPI) для аутентификации с использованием Kerberos для серверов Ldap (Active-Directory, ipa, rhds, Tivoli-DS). Ищите код в engine\backend\manager\modules\bll\src\main\java\org\ovirt\engine\core\bll\adbroker

Вы можете использовать git для клонирования репозитория или просмотра с помощью ссылки gerrit