Аутентификация в Active Directory с помощью Java в Linux - PullRequest
72 голосов
/ 24 декабря 2008

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

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

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

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

Ответы [ 9 ]

95 голосов
/ 17 января 2009

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

  1. Kerberos - Kerberos обеспечивает единый вход (SSO) и делегирование, но веб-серверам также требуется поддержка SPNEGO для принятия единого входа через IE.

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

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

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

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

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

Но если аутентифицирующие стороны не имеют обеих учетных записей домена или если клиент не может связаться с DC, NTLM требуется. Таким образом, Kerberos и NTLM не являются взаимоисключающими и NTLM не устарел в Kerberos. Фактически, в некоторых отношениях NTLM лучше, чем Kerberos. Обратите внимание, что при одновременном упоминании Kerberos и NTLM я должен также упомянуть SPENGO и встроенную аутентификацию Windows (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 в этой области, я не буду давать никаких обещаний относительно их реализации SPENGO, пока у вас не окажется эта птица.

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

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

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

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

Mike

49 голосов
/ 26 декабря 2008

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

}
6 голосов
/ 24 декабря 2008

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

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

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

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

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

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

5 голосов
/ 20 ноября 2009

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


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, который не поддерживается my AD-серверами.

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

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

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

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

3 голосов
/ 24 декабря 2008

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

2 голосов
/ 04 ноября 2009

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

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

1 голос
/ 19 ноября 2011

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

1 голос
/ 24 декабря 2008
0 голосов
/ 18 июня 2012

Рекомендую взглянуть на пакет 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

...