Список серверов Ldap в домене и аутентификация - PullRequest
2 голосов
/ 30 июня 2011

Как получить список серверов LDAP по доменному имени (используя java + acitvedirectory) и аутентифицировать его на основе имени пользователя и пароля?

Ответы [ 3 ]

6 голосов
/ 01 июля 2011

Вот шаги, чтобы сделать это

GET Server List

  1. Нажмите DNS-сервер, чтобы получить записи SRV
  2. Сортировать записи SRV
  3. Фильтрация записей сервера по шаблону регулярных выражений
  4. Создание URL-адресов LDAP на основе того, нужен ли нам ssl или нет. Примечание , что запись srv может возвращать только один порт, поэтому не полагайтесь на порт, возвращенный из записи srv. См. srv record RFC

Аутентификация

  1. перебрать список серверов
    1. Создайте Hashtable со средами ldap. обратите внимание, что нужно добавить имя домена с именем пользователя, разделенным \, и добавить URL-адрес ldap к нему
    2. попробуйте создать InitialDirContext
    3. успех: закрыть контекст и вернуться
    4. fail1: если исключение именования равно AuthenticationException и содержит сообщение "[LDAP: код ошибки 49" , переводите и генерируйте читаемое исключение! см отображение кода ошибки
    5. ошибка2: если нет ошибки1, переходите к следующему URL-адресу ldap

А вот и полноценный рабочий код

Если вы используете пружину, используйте следующую команду conf для вызова метода init

<bean
    class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
    <property name="targetClass">
        <value>org.bhate.ldap.LdapUtil</value>
    </property>
    <property name="targetMethod" value="init" />
    <property name="arguments">
        <list>
            <value>${ldap.dnsServer}</value>
            <value>${ldap.domainName}</value>
            <value>${ldap.filter.regexp}</value>
            <value>${ldap.ssl}</value>
        </list>         
    </property>

и следующие свойства

ldap.dnsServer=uk.mydomain.com
ldap.domainName=DOMAINNAME
ldap.filter.regexp=serv10.*|server.*
ldap.ssl=true

еще вызовите init во время запуска

LdapUtil.init("uk.mydomain.com", "DOMAINNAME", "serv10.*|server.*",true);

LdapUtil.java

package org.bhate.ldap;

import java.util.Collection;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.TreeSet;
import java.util.regex.Pattern;

import javax.naming.AuthenticationException;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.spi.NamingManager;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.NestedExceptionUtils;
import org.springframework.security.BadCredentialsException;


public class LdapUtil {

    private static final Logger LOGGER = LoggerFactory
            .getLogger(LdapUtil.class);

    @SuppressWarnings("serial")
    final static Map<String, String> errorCodesMap = new HashMap<String, String>() {
        {
            put("525", "user not found");
            put("52e", "invalid credentials");
            put("530", "not permitted to logon at this time");
            put("531", "not permitted to logon at this workstation");
            put("532", "password expired");
            put("533", "account disabled");
            put("701", "account expired");
            put("773", "user must reset password");
            put("775", "user account locked");
        }
    };

    private static Collection<String> ldapServers;
    private static String domainName;
    private static Pattern PATTERN;

    public static DirContext getDirContext(String url, String domainName,
            String userName, String password) throws NamingException {
        Hashtable<String, Object> env = getEnv(url, domainName, userName,
                password);
        return new InitialDirContext(env);
    }

    public static Collection<String> getLdapServers(String ldapDomain,
            boolean useSsl) throws NamingException {
        Collection<String> serverRecords = getSRVRecords(ldapDomain);
        serverRecords = reOrder(serverRecords);
        Collection<String> serverNames = new LinkedHashSet<String>();
        String protocol = "ldap" + (useSsl ? 's' : "");
        for (String s : serverRecords) {
            String hostName = s.substring(s.lastIndexOf(' ') + 1,
                    s.length() - 1);
            serverNames.add(protocol + "://" + hostName);
        }
        return serverNames;
    }

    private static Collection<String> reOrder(Collection<String> serverRecords) {
        return serverRecords;
    }

    private static Collection<String> getSRVRecords(String ldapDomain)
            throws NamingException {
        DirContext context = (DirContext) NamingManager.getURLContext("dns",
                new Hashtable<String, Object>());
        String ldapDNSUrl = "dns:///_ldap._tcp." + ldapDomain;
        String[] attrIds = { "SRV" };
        Attributes attributes = context.getAttributes(ldapDNSUrl, attrIds);
        Attribute servers = attributes.get("SRV");
        int L = servers.size();
        Collection<String> serverRecords = new TreeSet<String>();
        for (int i = 0; i < L; i++) {
            String s = (String) servers.get(i);
            if (PATTERN.matcher(s).find())
                serverRecords.add(s);
        }
        return serverRecords;
    }

    public static void authenticate(String userName, String password) {
        String msg = "Unable authenticate user {} with {}";
        for (String url : ldapServers) {
            DirContext ctx = null;
            try {
                ctx = getDirContext(url, domainName, userName, password);
                LOGGER
                        .info("Authenticated user {} on server {}", userName,
                                url);
                return;
            } catch (NamingException e) {
                LOGGER.error(msg, userName, url);
                String m = NestedExceptionUtils.buildMessage(e.getMessage(), e
                        .getCause());
                LOGGER.error(m, e);
                if (e instanceof AuthenticationException
                        && e.getMessage().startsWith("[LDAP: error code 49"))
                    throwMeanigfulEx(userName, url, e);
            } finally {
                close(ctx);
            }
        }
        LOGGER.error(msg, userName, "any available server");
        throw new BadCredentialsException(
                "Unable to authenticate : Please contact application support team");
    }

    public static void init(String dnsServerName, String domainName,
            String serverFilter, boolean useSsl) {
        PATTERN = Pattern.compile(serverFilter);
        try {
            LdapUtil.domainName=domainName;
            ldapServers = getLdapServers(dnsServerName, useSsl);

            if (LOGGER.isInfoEnabled()) {
                LOGGER.info(
                        "LDAP servers available in domain {} to connect {}",
                        dnsServerName, ldapServers);
            }
        } catch (NamingException e) {
            throw new RuntimeException("Unable retrieve ldapServers for "
                    + dnsServerName, e);
        }
    }

    private static void throwMeanigfulEx(String userName, String url,
            NamingException e) {
        String separator = ", data ";
        String m = e.getMessage();
        int strt = m.lastIndexOf(separator) + separator.length();
        int end = m.lastIndexOf(", vece");
        String code = m.substring(strt, end);
        throw new BadCredentialsException("Unable to authenticate : "
                + errorCodesMap.get(code));
    }

    private static void close(DirContext ctx) {
        if (ctx != null)
            try {
                ctx.close();
            } catch (NamingException e) {
                LOGGER.error("Unable to close context", e);
            }
    }

    private static Hashtable<String, Object> getEnv(String url,
            String domainName, String userName, String password) {
        Hashtable<String, Object> env = new Hashtable<String, Object>();
        env.put(Context.INITIAL_CONTEXT_FACTORY,
                "com.sun.jndi.ldap.LdapCtxFactory");
        env.put(Context.REFERRAL, "follow");
        env.put(Context.PROVIDER_URL, url);
        env.put("com.sun.jndi.ldap.connect.timeout", "2000");
        // env.put("com.sun.jndi.ldap.trace.ber", System.err);
        // env.put("javax.net.ssl.trustStoreType", "JKS");
        // env.put(Context.SECURITY_PROTOCOL, "ssl");
        env.put(Context.SECURITY_AUTHENTICATION, "simple");
        env.put(Context.SECURITY_PRINCIPAL, getPrincipal(domainName, userName));
        env.put(Context.SECURITY_CREDENTIALS, password);
        return env;
    }

    private static Object getPrincipal(String domainName, String userName) {
        return domainName + "\\" + userName;
    }


    public static void main(String[] args) throws NamingException {
        init("uk.mydomain.com", "DOMAINNAME", "serv10.*|server.*",true);
        System.out.println(ldapServers.size());
        System.out.println(ldapServers);
        if (args.length == 2)
            authenticate(args[0], args[1]);
    }


    //
    // public static InitialLdapContext getLdapContext(String userName,
    // String domainName, String password, String url)
    // throws NamingException {
    // Hashtable<String, Object> env = getEnv(url, domainName, userName,
    // password);
    // Control[] connCtls = new Control[] { new Control() {
    // private static final long serialVersionUID = 1L;
    //
    // public byte[] getEncodedValue() {
    // return null;
    // }
    //
    // public String getID() {
    // return "1.2.840.113556.1.4.1781";
    // }
    //
    // public boolean isCritical() {
    // return true;
    // }
    // } };
    // return new InitialLdapContext(env, connCtls);
    // }


}
2 голосов
/ 30 июня 2011

Вот фрагмент для поиска записей SRV в Java, используйте его с доменом, заданным ShaMan (_ldap._tcp.dc._msdcs.your.domain.com).

private static final String[] SRV = new String[] { "SRV" };

public static Collection<InetSocketAddress> srv(String name)
    throws NamingException
{
    DirContext ctx = new IntialDirContext();

    Attributes attrs = ctx.getAttributes("dns:/" + name, SRV);
    if(attributes.get("SRV") == null)
    {
        return Collections.emptyList();
    }

    NamingEnumeration<?> e = attributes.get("SRV").getAll();
    TreeMap<Integer, InetSocketAdress> result = new TreeMap<Integer, InetSocketAdress>();

    while(e.hasMoreElements())
    {
        String line = (String) e.nextElement();

        // The line is priority weight port host
        String[] parts = line.split("\\s+");

        int prio = Integer.parseInt(parts[0]);
        int port = Integer.parseInt(parts[2]);
        String host = parts[3];

        result.put(prio, new InetSocketAddress(host, port));
    }

    return result.values();
}
1 голос
/ 30 июня 2011

Вы можете получить список серверов LDAP для данного домена AD, прочитав записи SRV для этого домена. Запись SRV, в которой вы нуждаетесь, имеет значение, подобное этому _ldap._tcp.dc._msdcs.your.domain.com. Эта статья должна дать вам больше информации: http://technet.microsoft.com/en-us/library/cc738991%28WS.10%29.aspx.

Вы можете использовать JNDI для получения информации DNS, а затем для аутентификации через LDAP. Хороший учебник можно найти здесь: http://download.oracle.com/javase/jndi/tutorial/.

...