和利用数据库进行验证类似,LDAP中也是利用登陆名和密码进行验证,LDAP中会定义一个属性password,用来存放用户密码,而登陆名使用较多的都是邮箱地址。

下面是一个正确而又通用的步骤:
       1. 从客户端得到登录名和密码。注意这里的登录名和密码一开始是没有被用到的。

       2. 先匿名绑定到LDAP服务器,如果LDAP服务器没有启用匿名绑定,一般会提供一个默认的用户,用这个用户进行绑定即可。

       3. 之前输入的登录名在这里就有用了,当上一步绑定成功以后,需要执行一个搜索,而filter就是用登陆名来构造,例如: "(|(uid=$login)(mail=$login))" ,这里的login就是登录名。搜索执行完毕后,需要对结果进行判断,如果只返回一个entry,这个就是包含了该用户信息的entry,可以得到该entry的DN,后面使用。如果返回不止一个或者没有返回,说明用户名输入有误,应该退出验证并返回错误信息。

       4. 如果能进行到这一步,说明用相应的用户,而上一步执行时得到了用户信息所在的entry的DN,这里就需要用这个DN和第一步中得到的password重新绑定LDAP服务器。

       5. 执行完上一步,验证的主要过程就结束了,如果能成功绑定,那么就说明验证成功,如果不行,则应该返回密码错误的信息。

       这5大步就是基于LDAP的一个 “两次绑定” 验证方法。

        为什么基于LDAP进行验证需要“两次”绑定呢,为什么不能取出password然后和输入进行比较呢,试想一下,如果需要读出密码,服务器上的密码存储要么就不加密,直接可以读出,要么客户就需要知道服务器使用的加密方式,这是不安全,也是不好的,服务器不希望加密方式让客户端知道,客户端也不需要知道这么多。而从实际来看,LDAP服务器对于password属性默认都是不可读的,甚至有的服务器根本就不支持password属性可读,遇到这种情况,也就没有办法取得密码了。

       还有一个问题就是,为什么我们需要第一次绑定?为什么不直接使用DN呢,首先就是关于这个DN,对于一般的客户端程序,其并不知道具体的DN是什么。再者让用户输入DN,给用户带来不便的同时,验证也带来问题,因为如果输入的是个目录树而不是所期望的DN,在进行绑定时有可能会让服务器产生不可预料的错误。

       从上面看来,基于LDAP进行身份验证,最好也是最通用的方法就是 “两次绑定”。所谓的绑定是一个authentication(身份验证)的过程,不要把它想像成“绑定”,既然是认证,就需要一个用户名和密码,openldap中如果出示的用户名和密码错误,服务器会尝试匿名认证,就和匿名ftp一样。当然,在现实配置中可能需要在认证不获得成功就不能做查询操作,这些是在slapd.conf文件中通过设置ACL实现的。认证所用的用户名和密码为目录树中某个节点的两个属性(用户名和密码),一般情况下,程序会默认使用uid和userPassword属性。写程序进行认证的时候只要提供这个节点的两个属性就可以了。

ldap中常见名词解释:

CN:common name 通用名 

对象的属性为CN,例如一个用户的名字为:张三,那么“张三”就是一个CN。

O:organizationName 组织名

OU : OrganizationUnit 组织单位

o和ou都是ldap目录结构的一个属性,建立目录的时候可选新建o,ou 等。在配置我司交换设备ldap的时候具体是配置ou,o还是cn等,要具体看ldap服务器的相应目录是什么属性。

UID: userid

对象的属性为uid,例如我司一个员工的名字为:zsq,他的UID为:z02691,ldap查询的时候可以根据cn,也可以根据uid。配置ldap查询的时候需要考虑用何种查询方式。具体设备配置根据何种方式查询由ldap服务器的相关配置来决定。

DC:Domain Component

DC类似于dns中的每个元素,例如h3c.com,“.”符号分开的两个单词可以看成两个DC。

DN:Distinguished Name

类似于DNS,DN与DNS的区别是:组成DN的每个值都有一个属性类型,例如:

H3c.com是一个dns,那么用dn表示为:dc=h3c,dc=com 级别越高越靠后。H3c和com的属性都是DC。

DN可以表示为ldap的某个目录,也可以表示成目录中的某个对象,这个对象可以是用户等。

 

下面是一个简单的ldap认证流程代码:

import org.junit.Before;
import org.junit.Test;
 
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.Control;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.LdapContext;
import javax.xml.bind.PropertyException;
import java.util.*;
 
 
public class LdapTest {
 
    private final String PROPERTIES_SCOPE = "ldap";
 
    private final String FACTORY = "com.sun.jndi.ldap.LdapCtxFactory";
    private  String url = "ldap://192.168.30.254:389";
    private  String base = "1";
    private  LdapContext ctx = null;
    private  Control[] connCtls = null;
    public static LdapContext ctx = null;
    private final Control[] connCtls = null;
 
    @Test
    public void testUser() {
        connLDAP();
        List<User> list = getUser("users");
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
        System.out.println("ending");
    }
    @Test
    public void test() {
        connLDAP();
    }
    public boolean connLDAP() {
         
        Hashtable<String, String> env = new Hashtable<>();
        env.put(Context.INITIAL_CONTEXT_FACTORY, FACTORY);
        env.put(Context.PROVIDER_URL, url);
        env.put(Context.SECURITY_AUTHENTICATION, "simple");
        env.put(Context.SECURITY_PRINCIPAL, ldapUserDn);
        env.put(Context.SECURITY_CREDENTIALS, ldapPassword);
 
        try {
            ctx = new InitialLdapContext(env, connCtls);
            System.out.println("认证成功!");
            return Boolean.TRUE;
 
        } catch (javax.naming.AuthenticationException e) {
            System.out.println("认证失败 :");
            e.printStackTrace();
            return Boolean.FALSE;
        } catch (Exception e) {
            System.out.println("认证出错 : ");
            e.printStackTrace();
            return Boolean.FALSE;
        } finally {
            close();
        }
    }
    public void close() {
        if (ctx != null) {
            try {
                ctx.close();
                System.out.println("连接关闭");
            } catch (NamingException e) {
                e.printStackTrace();
            }
        }
    }
public List<User> getUser(String uname) {
        List<User> users = new ArrayList<>();
        HashMap<String, String> map = new HashMap<>();
        SearchControls searchCtls = new SearchControls();
        searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);
        //过滤器,可改变查询条件
        String searchFilter = "(&(cn=" + uname + ")(objectClass=person))";
        String searchBase = "DC=maxcrc,DC=com";
        //对象的每个属性名
        String[] returnedAtts = {"mail", "cn", "userPassword", "sn", "uid"};
        searchCtls.setReturningAttributes(returnedAtts);
        NamingEnumeration<SearchResult> answer;
        Map<String, String> mmap = new HashMap<>();
        try {
            answer = ctx.search(searchBase, searchFilter, searchCtls);
            while (answer.hasMoreElements()) {
                SearchResult sr = (SearchResult) answer.next();
                String name = sr.getName();
                User user = new User();
                if (name.contains("cn") || name.contains("uid")) {
                    String[] s = name.split(",");
                    String[] cns = s[0].split("=");
                    String cn = cns[cns.length - 1];
                    mmap.put(cn, sr.getNameInNamespace());
                    NamingEnumeration<? extends Attribute> attrs = sr.getAttributes().getAll();
                    while (attrs.hasMore()) {
                        Attribute attr = attrs.next();
                        switch (attr.getID()) {
                            case "cn":
                                user.setCn(attr.get().toString());
                                break;
                            case "sn":
                                user.setSn(attr.get().toString());
                                break;
                            case "mail":
                                user.setMail(attr.get().toString());
                                break;
                            case "uid":
                                user.setUid(attr.get().toString());
                                break;
                            case "userPassword":
                                user.setPassword(attr.get().toString());
                                break;
                        }
                    }
                    users.add(user);
                }
            }
        } catch (NamingException e) {
            e.printStackTrace();
        }
        mmap.forEach((k, v) -> System.out.println("key : value = " + k + ":" + v));
        return users;
    }
}
public class User {
    private String cn;
    private String sn;
    private String mail;
    private String uid;
    private String password;
 
    public String getCn() {
        return cn;
    }
 
    public void setCn(String cn) {
        this.cn = cn;
    }
 
    public String getSn() {
        return sn;
    }
 
    public void setSn(String sn) {
        this.sn = sn;
    }
 
    public String getMail() {
        return mail;
    }
 
    public void setMail(String mail) {
        this.mail = mail;
    }
 
    public String getUid() {
        return uid;
    }
 
    public void setUid(String uid) {
        this.uid = uid;
    }
 
    public String getPassword() {
        return password;
    }
 
    public void setPassword(String password) {
        this.password = password;
    }
 
    @Override
    public String toString() {
        return "User{" +
                "cn='" + cn + '\'' +
                ", sn='" + sn + '\'' +
                ", mail='" + mail + '\'' +
                ", uid='" + uid + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}