Автентифікація щодо Active Directory за допомогою Java на Linux


75

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

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

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

Хтось має приклад робочого коду? Дякую.

Відповіді:


98

Існує 3 протоколи автентифікації, які можна використовувати для автентифікації між Java та Active Directory на Linux або будь-якій іншій платформі (і це не тільки для служб HTTP):

  1. Kerberos - Kerberos забезпечує єдиний вхід (SSO) та делегування, але веб-сервери також потребують підтримки SPNEGO, щоб прийняти SSO через 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 існує безкоштовний проект OSS під назвою JCIFS, який має фільтр сервлетів автентифікації HTTP NTLM. Однак він використовує метод "людина посередині" для перевірки облікових даних із сервером SMB, який не працює з NTLMv2 (що повільно стає необхідною політикою безпеки домену). З цієї причини та інших, частину фільтру HTTP JCIFS планується видалити. Зверніть увагу, що існує кілька виділень, які використовують JCIFS для реалізації тієї самої техніки. Тож якщо ви бачите інші проекти, які заявляють, що підтримують NTLM SSO, перевірте дрібний шрифт.

Єдиним правильним способом перевірки облікових даних NTLM за допомогою Active Directory є використання виклику NetrLogonSamLogon DCERPC через NETLOGON із захищеним каналом. Чи існує таке в Java? Так. Ось:

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

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

Майк


5
Оновлення: HttpClient тепер підтримує схеми автентифікації Basic, Digest, NTLMv1, NTLMv2, NTLM2 Session, SNPNEGO, Kerberos.
Джонатан Барберо,

52

Ось код, який я склав на основі прикладу з цього блогу: 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();
    }

}

4
import com.sun.jndi.ldap.LdapCtxFactory;- це, швидше за все, працюватиме лише з Sun JVM.
Thorbjørn Ravn Andersen

6

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

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

Я б подивився: Spring LDAP

Вони теж мають приклади.

Що стосується шифрування, ми використовували з'єднання SSL (так це було LDAPS). AD потрібно було налаштувати на порту / протоколі SSL.

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


Лучіані, я зараз розробляю веб-додаток для інтеграції Java Spring для спільного використання точки (Windows), поки я не можу створити користувача в активному каталозі за допомогою Java-коду, чи можете ви поділитися своїм кодом для створення користувача в активному каталозі з коментарем, що я можу продовжувати свою роботу вчасно.
Янак Дханані

5

Як сказали 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, який не підтримується моїми серверами AD.

Справжній фокус полягає у пошуку сервера та хосту; офіційний спосіб полягає у використанні пошуку записів DNS SRV (служби) для пошуку набору хостів-кандидатів, після чого виконується LDAP-запит LDAP на основі UDP (у певному форматі Microsoft), щоб знайти правильний сервер. Якщо вам цікаво, я опублікував декілька статей у своєму блозі про свою подорож пригод і відкриттів у цій області.

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

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

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


Дуже корисні записи в блозі. Дякую!
Андрій Родіонов

3

Ви просто перевіряєте облікові дані? У такому випадку ви могли б просто зробити kerberosі не турбуватися LDAP.


Так, лише перевірка облікових даних. Я відредагував питання з роз’ясненнями. Чи відрізняється код від LDAP auth?
DV.

2

Якщо все, що ви хочете зробити, це аутентифікація проти AD за допомогою Kerberos, тоді це має зробити проста програма http://spnego.sourceforge.net/HelloKDC.java .

Погляньте на «передполітну» документацію проекту, яка розповідає про програму HelloKDC.java.



1

Аутентифікація ldap без SSL не є безпечною, і кожен може переглядати облікові дані користувача, оскільки клієнт ldap передає ім'я користувача та пароль під час операції прив'язки ldap. Тому завжди використовуйте протокол ldaps. джерело: Ldap-автентифікація Активний каталог у Java Spring Security з прикладом


0

Я рекомендую вам ознайомитися з пакетом рекламного брокера проекту oVirt . Він використовує Spring-Ldap та модуль входу Krb5 JAAS (з 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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.