簡體   English   中英

列出域中的 Ldap 服務器並進行身份驗證

[英]List Ldap servers in the domain and authenticate

如何獲取給定域名的 LDAP 服務器列表(使用 java + acitvedirectory),並根據用戶名和密碼對其進行身份驗證?

這是執行此操作的步驟

獲取服務器列表

  1. 點擊 DNS 服務器獲取 SRV 記錄
  2. 對 SRV 記錄進行排序
  3. 基於正則表達式模式過濾服務器記錄
  4. 根據我們是否需要 ssl 來構造 LDAP url。 請注意,srv 記錄只能返回一個端口,因此不要依賴從 srv 記錄返回的端口。 參見srv 記錄 RFC

驗證

  1. 遍歷服務器列表
    1. 使用 ldap 環境構建 Hashtable。 注意需要append域名,用戶名用\分隔,並添加ldap URL
    2. 嘗試創建 InitialDirContext
    3. 成功:關閉上下文並返回
    4. failure1:如果命名異常是 AuthenticationException 並且包含消息“[LDAP:錯誤代碼 49” ,則翻譯並拋出可讀異常! 查看錯誤代碼映射
    5. failure2 如果不是 failure1,則繼續下一個 ldap URL

這是完整的工作代碼

如果您使用 spring 使用以下 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);
    // }


}

這是在 Java 中查找 SRV 記錄的片段,將其與 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();
}

您可以通過讀取該域的 SRV 記錄來獲取給定 AD 域的 LDAP 服務器列表。 您需要的 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/

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM