JAVA,OpenLDAP使用心得

1 基本概念
LDAP 是一个基于 X.500 标准的轻量目录访问协议,全称为 Lightweight Directory Access Protocol。

目录服务:一个特殊的数据库,用来保存描述性的、基于属性的详细信息,支持过滤功能,动态灵活且易扩展的。目录是一个为查询、浏览和搜索而优化的数据库,它成树状结构组织数据,类似文件目录一样。
目录数据库的特点:以树状结构存储数据,读取速度快,写入速度慢。没有事务处理、回滚等复杂功能,不适于存储修改 - 频繁的数据。
LDAP 是开放的标准化协议,受到广泛支持。它以树形用户目录为存储结构,对组织管理建模符合通常认知。
1.1 LDAP = 目录数据库 + 访问协议
LDAP 采用 client-server 模型,服务器用于存放数据,客户端用于操作数据。LDAP 协议的具体实现总结如下表:

1.2 数据模型
在 LDAP 中,信息以树状方式组织,在树状信息中的基本数据单元是条目,而每个条目由属性构成,属性中存储有属性值。

以下结合一个 OpenLDAP 里面的数据来解释下相关术语

Entry
条目,也叫记录项,是 LDAP 中最基本的颗粒,就像字典中的词条,或者是数据库中的记录。通常对 LDAP 的添加、删除、更改、检索都是以条目为基本对象的。(注意:一个 Entry 可以是一个 User 也可以是一个 OU 也可以是一个 Group 都行)

dn:每一个条目都有一个唯一的标识名(distinguished Name ,DN)(相当于一个表里面会有一个唯一的标识字段)。通过 DN 的层次型语法结构,可以方便地表示出条目在 LDAP 树中的位置,通常用于检索。

rdn:一般指 dn 逗号最左边的部分,如 cn=dev1。

它与 RootDN 不同,RootDN 通常与 RootPW 同时出现,特指管理 LDAP 中信息的最高权限用户。

Base DN:LDAP 目录树的最顶部就是根 (这树目录树的根节点),也就是所谓的 “Base DN”,如图:”dc=landingzone,dc=com”。

Attribute
每个条目都可以有很多属性(Attribute),比如常见的人都有姓名、地址、电话等属性。每个属性都有名称及对应的值,属性值可以有单个、多个,比如你有多个邮箱。

属性不是随便定义的,需要符合一定的规则,而这个规则可以通过 schema 制定。比如

那针对 posixGroup 的类型,属性的修改就得这样

objectClass:表示这个 Entry 的类型,这块是由 Schema 定义出来的,需要有哪些属性。后面可以编辑这个 Entry 扩展一些属性。

LDAP 为人员组织机构中常见的对象都设计了属性 (比如 commonName,surname)。下面有一些常用的别名:

ObjectClass
对象类是属性的集合,LDAP 预想了很多人员组织机构中常见的对象,并将其封装成对象类。

比如:

人员(person)含有姓(sn)、名(cn)、电话 (telephoneNumber)、密码 (userPassword) 等属性

单位职工 (organizationalPerson) 是人员 (person) 的继承类,除了上述属性之外还含有职务(title)、邮政编码(postalCode)、通信地址 (postalAddress) 等属性。

通过对象类可以方便的定义条目类型。每个条目可以直接继承多个对象类,这样就继承了各种属性。如果 2 个对象类中有相同的属性,则条目继承后只会保留 1 个属性。对象类同时也规定了哪些属性是基本信息,必须含有 (Must 活 Required,必要属性):哪些属性是扩展信息,可以含有(May 或 Optional,可选属性)。

对象类有三种类型:结构类型(Structural)、抽象类型 (Abstract) 和辅助类型(Auxiliary)。结构类型是最基本的类型,它规定了对象实体的基本属性,每个条目属于且仅属于一个结构型对象类。抽象类型可以是结构类型或其他抽象类型父类,它将对象属性中共性的部分组织在一起,称为其他类的模板,条目不能直接集成抽象型对象类。辅助类型规定了对象实体的扩展属性。每个条目至少有一个结构性对象类。

Schema
对象类(ObjectClass)、属性类型(AttributeType)、语法(Syntax)分别约定了条目、属性、值

值的类型约束

TLS & SASL
分布式 LDAP 是以明文的格式通过网络来发送信息的,包括 client 访问 ldap 的密码(当然一般密码已然是二进制的),SSL/TLS 的加密协议就是来保证数据传送的保密性和完整性。

SASL (Simple Authenticaion and Security Layer)简单身份验证安全框架,它能够实现 openldap 客户端到服务端的用户验证,也是 ldapsearch、ldapmodify 这些标准客户端工具默认尝试与 LDAP 服务端认证用户的方式(前提是已经安装好 Cyrus SASL)。SASL 有几大工业实现标准:Kerveros V5、DIGEST-MD5、EXTERNAL、PLAIN、LOGIN。

Kerveros V5 是里面最复杂的一种,使用 GSSAPI 机制,必须配置完整的 Kerberos V5 安全系统,密码不再存放在目录服务器中,每一个 dn 与 Kerberos 数据库的主体对应。DIGEST-MD5 稍微简单一点,密码通过 saslpasswd2 生成放在 sasldb 数据库中,或者将明文 hash 存到 LDAP dn 的 userPassword 中,每一个 authid 映射成目录服务器的 dn,常和 SSL 配合使用。参考将 LDAP 客户端配置为使用安全性。

LDIF
LDIF(LDAP Data Interchange Format,数据交换格式)是 LDAP 数据库信息的一种文本格式,用于数据的导入导出,每行都是 “属性:值” 对。

2 LDAP 服务器搭建
LDAP = 目录数据库 + 访问协议。因此要实现 LDAP 登录,首先要搭建一套自己的 LDAP 服务器,以供开发过程的自测,以及提测后的测试。然而 LDAP 服务器厂商众多,本次主要挑选 Linux 环境的 OpenLDAP 服务器,搭建过程和遇到的一些问题总结如下。

2.1 OpenLDAP 服务器的搭建
环境准备
准备一台 Linux 主机并安装有 docker 环境。建议直接购买一台云服务器,镜像可以选择如 Docker 运行环境 (CentOS7.2 64 位),这样自带 docker 环境免安装。
通过 docker 来搭建
docker run –name ldap-server \ –hostname ldap-server \ -p 389:389 -p 636:636 \ –detach \ osixia/openldap:latest
这个镜像是使用了自签凭证,使用 TLS 方式連線時會出現遠端憑證無效的錯誤。解决办法:

1、執行 docker cp ldap-server:/container/service/slapd/assets/certs/ldap.crt . 將自動產生的自簽憑證複製到本機的當前目錄中。

2、安裝 ldap.crt 憑證到本機中,並設定為信任憑證 (或單獨設定信任 X509 初級規則)。

如果不想使用默认的凭证,也可以自动生成。比如通过 OpenSSL 自签名生成。另外这个镜像的证书位置

CA 憑證檔案路徑:/container/service/slapd/assets/certs/ca.crtLDAP 憑證檔案路徑:/container/service/slapd/assets/certs/ldap.crtLDAP 憑證 Key 檔案路徑:/container/service/slapd/assets/certs/ldap.key
通过这个命令,创建出来的 OpenLDAP 会有几个默认参数

Port: 389 / 636 (TLS)Admin DN: cn=admin,dc=example,dc=orgAdmin Password: admin
自定义凭证 - 生成自定义证书
1、通过 openssl 生成私钥

openssl genrsa -out server.key 1024
2、根据私钥生成证书申请文件 csr

openssl req -new -key server.key -out server.csr
ps:Common Name 可以输入:*.yourdomain.com,这种方式生成通配符域名证书

3、使用私钥对证书申请进行签名从而生成证书

openssl x509 -req -in server.csr -out server.crt -signkey server.key -days 3650
自定义凭证 - 启动容器时挂载到容器
docker run –name ldap-server –hostname ldap-server -p 389:389 -p 636:636 –volume /path/ssl:/container/service/slapd/assets/certs –detach osixia/openldap:latest
通过 - v 挂载宿主机的目录到容器进而完成使用自定义证书。

通过环境变量修改一些自定义 LDAP 设定
LDAP_ORGANISATION:設定 LDAP 的組織名稱,預設為 Example Inc.。

LDAP_DOMAIN:設定 LDAP 的組織域名,預設為 example.org。

LDAP_ADMIN_PASSWORD:設定 LDAP 的管理員密碼,預設為 admin。

LDAP_TLS_VERIFY_CLIENT:設定 LDAP 是否驗證客戶端憑證,如想關掉可以設為 try,預設為 demand。

LDAP_TLS_CRT_FILENAME:LDAP SSL 憑證檔案名稱,預設為 ldap.crt。

LDAP_TLS_KEY_FILENAME:LDAP SSL 憑證 Key 檔案名稱,預設為 ldap.key。

LDAP_TLS_CA_CRT_FILENAME:CA 憑證檔案名稱,預設為 ca.crt。

搭建测试环境
为了简化后续操作,关闭 TLS 认证。就用下面这条启动指令:

docker run –name ldap-server –hostname ldap-server -p 389:389 -p 636:636 -e LDAP_TLS_VERIFY_CLIENT=”try” -e LDAP_DOMAIN=”landingzone.com” -e LDAP_ADMIN_PASSWORD=”admin1” –detach osixia/openldap:latest
搭建 LDAP 管理界面
docker run –name ldap-admin -p 6443:443 –link ldap-server:ldap-host –env PHPLDAPADMIN_LDAP_HOSTS=ldap-host –detach osixia/phpldapadmin:latest
如果是本地的话那就可以直接浏览器访问:localhost:6443 就可以打开界面了。相关登录账号与密码:

Admin DN(用户名): cn=admin,dc=landingzone,dc=com

Admin Password(密码): admin1

登录成功的界面

通过 LDAP 管理界面增删改 LDAP 数据
添加 Entry

通过这个操作可以添加 OU\Group\Account 等企业内部的组织与员工信息。

如何安装已经在官方文档有了,一步步的照着做就可以

/usr/local/etc/openldap/slapd.conf 中可以找到 schema,pid 以及数据库文件存放的路径

我修改了 /usr/local/etc/openldap/slapd.conf 文件,但是发现没啥用,原来是忘了把 slapd 停止重新启动了。关于停止 slapd,官方给的是:kill -INT ‘cat /usr/local/var/slapd.pid’

但是我执行以后提示 bash: kill: cat /usr/local/var/slapd.pid: arguments must be process or job IDs

用 find /usr -name slapd.pid 命令找到了在 /usr/local/var/run/ 下,把命令改为:

kill -INT cat /usr/local/var/run/slapd.pid

重新运行 slapd:su root -c /usr/local/libexec/slapd

建议执行 /usr/local/libexec/slapd -d256 命令,这样既可以在命令行看到出错信息,也可以用 Ctrl+C 停止进程

关于 rootpw,很多地方都说 rootpw 和密码值之间不能加空格,不然会出错。有个解决的办法:rootpw “secret” 加了双引号,只要输入的密码和引号里面的对应就可以了。

很多人在测试 ldapadd 命令时,都遇到过 ldap_bind: Invalid credentials (49) 错误,看看 rootdn “cn=Manager,dc=example,dc=com” 和自己的 ldif 里面的 dn 参数是不是匹配,如果不匹配就需要修改,修改后记得要停止重启哦(我还不知道怎么重新读取配置的方法,只能用这种笨方法了)

折腾了一天,终于初步了解 JAVA 怎么在 OpenLDAP 增加删除数据了。代码如下

/**
*

  • @author chenyi
  • /
    import java.util.Hashtable;
    import javax.naming.Context;
    import javax.naming.NamingException;
    import javax.naming.directory.;
    import java.util.
    ;

public class ChenYi {

DirContext ctx = null;
String account = "Manager";//操作LDAP的帐户。默认就是Manager。
String password = "secret";//帐户Manager的密码。
String root = "dc=example,dc=com"; //LDAP的根节点的DC

public ChenYi() {
    init();
    add();
    //delete();
    close();
}

public void init() {
    Hashtable env = new Hashtable();
    env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
    env.put(Context.PROVIDER_URL, "<ldap://192.168.100.221:389/>");      
    env.put(Context.SECURITY_AUTHENTICATION, "simple");
    env.put(Context.SECURITY_PRINCIPAL, "cn=" + account + "," + root);
    env.put(Context.SECURITY_CREDENTIALS, password);
    try {
        ctx = new InitialDirContext(env);//初始化上下文
        System.out.println("认证成功");//这里可以改成异常抛出。
    } catch (javax.naming.AuthenticationException e) {
        System.out.println("认证失败");
    } catch (Exception e) {
        System.out.println("认证出错:" + e);
    }
}

public void add() {
    try {
        String newUserName = "hi";
        BasicAttributes attrs = new BasicAttributes();
        BasicAttribute objclassSet = new BasicAttribute("objectClass");
        objclassSet.add("top");
        objclassSet.add("organizationalUnit");
        attrs.put(objclassSet);
        attrs.put("ou", newUserName);
        ctx.createSubcontext("ou=" + newUserName + "," + root, attrs);
    } catch (Exception e) {
        e.printStackTrace();
        System.out.println("Exception in add():" + e);
    }
}

public void delete() {
    try {
        ctx.destroySubcontext("ou=hi,dc=example,dc=com");
    } catch (Exception e) {
        e.printStackTrace();
        System.out.println("Exception in delete():" + e);
    }
}

public void close() {
    if (ctx != null) {
        try {
            ctx.close();
        } catch (NamingException e) {
            System.out.println("NamingException in close():" + e);
        }
    }
}

public static void main(String[] args) {
    new ChenYi();
}

}

红线标记的地方特别注意,我看很多文章中写的都类似于 env.put (Context.PROVIDER_URL, “ldap://localhost:7001/“+ root); 经过我一天的折腾发现加上了 root,会报 javax.naming.NameNotFoundException: [LDAP: error code 32 - No Such Object]; 错误。也许这是新版不兼容旧版程序吧

今天终于把添加,删除,修改节点名,属性,遍历节点都弄出来了,先把代码贴出来吧

/**
*

  • @author chenyi
  • /
    import java.util.Hashtable;
    import javax.naming.directory.;
    import java.util.
    ;
    import javax.naming.*;

public class ChenYi {

DirContext dc = null;
String account = "Manager";//操作LDAP的帐户。默认就是Manager。
String password = "secret";//帐户Manager的密码。
String root = "dc=example,dc=com"; //LDAP的根节点的DC

public ChenYi() {
    init();
    //add();//添加节点
    //delete("ou=hi,dc=example,dc=com");//删除"ou=hi,dc=example,dc=com"节点
    //modifyInformation("ou=hi,dc=example,dc=com");//修改"ou=hi,dc=example,dc=com"属性
    //renameEntry("ou=new,o=neworganization,dc=example,dc=com","ou=neworganizationalUnit,o=neworganization,dc=example,dc=com");//重命名节点"ou=new,o=neworganization,dc=example,dc=com"
    searchInformation("dc=example,dc=com", "", "(objectclass=*)");//遍历所有根节点
    //searchInformation("o=neworganization,dc=example,dc=com","","(objectclass=*)");//遍历指定节点的分节点
    close();
}

public void init() {
    Hashtable env = new Hashtable();
    env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
    env.put(Context.PROVIDER_URL, "<ldap://192.168.100.221:389/>");
    env.put(Context.SECURITY_AUTHENTICATION, "simple");
    env.put(Context.SECURITY_PRINCIPAL, "cn=" + account + "," + root);
    env.put(Context.SECURITY_CREDENTIALS, password);
    try {
        dc = new InitialDirContext(env);//初始化上下文
        System.out.println("认证成功");//这里可以改成异常抛出。
    } catch (javax.naming.AuthenticationException e) {
        System.out.println("认证失败");
    } catch (Exception e) {
        System.out.println("认证出错:" + e);
    }
}

public void close() {
    if (dc != null) {
        try {
            dc.close();
        } catch (NamingException e) {
            System.out.println("NamingException in close():" + e);
        }
    }
}

public void add() {
    try {
        String newUserName = "hi";
        BasicAttributes attrs = new BasicAttributes();
        BasicAttribute objclassSet = new BasicAttribute("objectClass");
        objclassSet.add("top");
        objclassSet.add("organizationalUnit");
        attrs.put(objclassSet);
        attrs.put("ou", newUserName);
        dc.createSubcontext("ou=" + newUserName + "," + root, attrs);
    } catch (Exception e) {
        e.printStackTrace();
        System.out.println("Exception in add():" + e);
    }
}

public void delete(String dn) {
    try {
        dc.destroySubcontext(dn);
    } catch (Exception e) {
        e.printStackTrace();
        System.out.println("Exception in delete():" + e);
    }
}

public boolean modifyInformation(String dn) {
    try {
        ModificationItem[] mods = new ModificationItem[1];

        /*添加属性*/

// Attribute attr0 = new BasicAttribute(“description”,
// “测试”);
// mods[0] = new ModificationItem(DirContext.ADD_ATTRIBUTE,attr0);

        /*修改属性*/

// Attribute attr0 = new BasicAttribute (“description”, “陈轶”);
// mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE,
// attr0);

        /*删除属性*/
        Attribute attr0 = new BasicAttribute("description",
                "陈轶");
        mods[0] = new ModificationItem(DirContext.REMOVE_ATTRIBUTE,
                attr0);
        dc.modifyAttributes(dn, mods);
        return true;
    } catch (NamingException ne) {
        ne.printStackTrace();
        System.err.println("Error: " + ne.getMessage());
        return false;
    }

}

/**
 * @param base :根节点(在这里是"dc=example,dc=com")
 * @param scope :搜索范围,分为"base"(本节点),"one"(单层),""(遍历)
 * @param filter :指定子节点(格式为"(objectclass=*)",*是指全部,你也可以指定某一特定类型的树节点)
 */
public void searchInformation(String base, String scope, String filter) {
    SearchControls sc = new SearchControls();
    if (scope.equals("base")) {
        sc.setSearchScope(SearchControls.OBJECT_SCOPE);
    } else if (scope.equals("one")) {
        sc.setSearchScope(SearchControls.ONELEVEL_SCOPE);
    } else {
        sc.setSearchScope(SearchControls.SUBTREE_SCOPE);
    }

    NamingEnumeration ne = null;
    try {
        ne = dc.search(base, filter, sc);
        // Use the NamingEnumeration object to cycle through
        // the result set.
        while (ne.hasMore()) {
            System.out.println();
            SearchResult sr = (SearchResult) ne.next();
            String name = sr.getName();
            if (base != null && !base.equals("")) {
                System.out.println("entry: " + name + "," + base);
            } else {
                System.out.println("entry: " + name);
            }

            Attributes at = sr.getAttributes();
            NamingEnumeration ane = at.getAll();

            while (ane.hasMore()) {
                Attribute attr = (Attribute) ane.next();
                String attrType = attr.getID();
                NamingEnumeration values = attr.getAll();
                Vector vals = new Vector();
                // Another NamingEnumeration object, this time
                // to iterate through attribute values.
                while (values.hasMore()) {
                    Object oneVal = values.nextElement();
                    if (oneVal instanceof String) {
                        System.out.println(attrType + ": " + (String) oneVal);
                    } else {
                        System.out.println(attrType + ": " + new String((byte[]) oneVal));
                    }
                }
            }
        }
    } catch (Exception nex) {
        System.err.println("Error: " + nex.getMessage());
        nex.printStackTrace();
    }
}

public boolean renameEntry(String oldDN, String newDN) {
    try {
        dc.rename(oldDN, newDN);
        return true;
    } catch (NamingException ne) {
        System.err.println("Error: " + ne.getMessage());
        return false;
    }
}

public static void main(String[] args) {
    new ChenYi();
}

}

经过几天的努力,把获取 objectClass 定义和获取 Attribute 定义的代码弄出来,这样就方便了以后根据自定义 schema 动态的获取 schema 中的 objectClass 和 Attribute。特别是对于做添加修改界面应该有点用处,修改了 schema 并不需要修改代码做代码调整,只需要根据获取的属性个数挨个排好,让别人填入值,并且可以检测 MUST 的是不是已经填写了。

/**
 * 获取指定objectClass的定义
 * @param name
 */
public void getObjectClassDefinition(String name) {
    try {
        // Get the schema tree root
        DirContext schema = dc.getSchema("");

        // Get the schema object for "person"
        DirContext personSchema = (DirContext) schema.lookup("ClassDefinition/" + name);
        Attributes a = personSchema.getAttributes("");
        NamingEnumeration ane = a.getAll();

        while (ane.hasMore()) {
            Attribute attr = (Attribute) ane.next();
            String attrType = attr.getID();
            NamingEnumeration values = attr.getAll();

            while (values.hasMore()) {
                Object oneVal = values.nextElement();
                if (oneVal instanceof String) {
                    System.out.println(attrType + ": " + (String) oneVal);
                } else {
                    System.out.println(attrType + ": " + new String((byte[]) oneVal));
                }
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

/**
 * 获取指定DN的objectClass定义
 * @param DN
 */
public void getDNObjectClassDefinition(String DN) {
    try {
        // Get context containing class definitions for the "cn=Ted Geisel" entry
        DirContext tedClasses = dc.getSchemaClassDefinition(DN);

        // Enumerate the class definitions
        NamingEnumeration enum1 = tedClasses.search("", null);
        while (enum1.hasMore()) {
            Object o = enum1.next();
            System.out.println(((SearchResult) o).getName());

            Attributes a = ((SearchResult) o).getAttributes();
            NamingEnumeration ane = a.getAll();

            while (ane.hasMore()) {
                Attribute attr = (Attribute) ane.next();
                String attrType = attr.getID();
                NamingEnumeration values = attr.getAll();

                while (values.hasMore()) {
                    Object oneVal = values.nextElement();
                    if (oneVal instanceof String) {
                        System.out.println(attrType + ": " + (String) oneVal);
                    } else {
                        System.out.println(attrType + ": " + new String((byte[]) oneVal));
                    }
                }
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

/**
 * 获取指定名字的Attribute定义
 * @param name
 */
public void getAttributeDefinition(String name) {
    try {
        // Get the schema tree root
        DirContext schema = dc.getSchema("");

        // Get the schema object for "person"
        DirContext personSchema = (DirContext) schema.lookup("AttributeDefinition/" + name);
        Attributes a = personSchema.getAttributes("");
        NamingEnumeration ane = a.getAll();

        while (ane.hasMore()) {
            Attribute attr = (Attribute) ane.next();
            String attrType = attr.getID();
            NamingEnumeration values = attr.getAll();

            while (values.hasMore()) {
                Object oneVal = values.nextElement();
                if (oneVal instanceof String) {
                    System.out.println(attrType + ": " + (String) oneVal);
                } else {
                    System.out.println(attrType + ": " + new String((byte[]) oneVal));
                }
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

/**
 * 获取指定DN中指定名字的Attribute定义
 * @param DN
 * @param name
 */
public void getDNAttributeDefinition(String DN, String name) {
    try {
        // Get an attribute of that type
        Attributes attrs = dc.getAttributes(DN, new String[]{name});
        Attribute cnAttr = attrs.get(name);

        // Get its attribute type definition
        DirContext cnSchema = cnAttr.getAttributeDefinition();
        // Get cnSchema's attributes
        Attributes cnAttrs = cnSchema.getAttributes("");

        NamingEnumeration ane = cnAttrs.getAll();
        while (ane.hasMore()) {
            Attribute attr = (Attribute) ane.next();
            String attrType = attr.getID();
            NamingEnumeration values = attr.getAll();

            while (values.hasMore()) {
                Object oneVal = values.nextElement();
                if (oneVal instanceof String) {
                    System.out.println(attrType + ": " + (String) oneVal);
                } else {
                    System.out.println(attrType + ": " + new String((byte[]) oneVal));
                }
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

了解 objectClass#

LDAP 中,一个条目必须包含一个 objectClass 属性,且需要赋予至少一个值。每一个值将用作一条 LDAP 条目进行数据存储的模板;模板中包含了一个条目必须被赋值的属性和可选的属性。objectClass 有着严格的等级之分,最顶层是 top 和 alias。例如,organizationalPerson 这个 objectClass 就隶属于 person,而 person 又隶属于 top。


objectClass 可分为以下 3 类:
结构型(Structural):如 person 和 organizationUnit;
辅助型(Auxiliary):如 extensibeObject;
抽象型(Abstract):如 top,抽象型的 objectClass 不能直接使用。
在 OpenLDAP 的 schema 中定义了很多 objectClass,下面列出部分常用的 objectClass 的名称。
● account
● alias
● dcobject
● domain
● ipHost
● organization
● organizationalRole
● organizationalUnit
● person
● organizationalPerson
● inetOrgPerson
● residentialPerson
● posixAccount
● posixGroup


了解 Attribute#

属性(Attribute)类似于程序设计中的变量,可以被赋值。在 OpenLDAP 中声明了许多常用的 Attribute(用户也可自己定义 Attribute)。常见的 Attribute 含义如下:
● c:国家。
● cn:common name,指一个对象的名字。如果指人,需要使用其全名。
● dc:domain Component,常用来指一个域名的一部分。
● givenName:指一个人的名字,不能用来指姓。
● l:指一个地名,如一个城市或者其他地理区域的名字。
● mail:电子信箱地址。
● o:organizationName,指一个组织的名字。
● ou:organizationalUnitName,指一个组织单元的名字。
● sn:surname,指一个人的姓。
● telephoneNumber:电话号码,应该带有所在的国家的代码。

提示:objectClass 是一种特殊的 Attribute,它包含其他用到的 Attribute 以及其自身。


对于不同的 objectClass,通常具有一些必设属性值和一些可选属性值。例如,可使用 person 这个 objectClass 来表示系统中一个用户的条目,对于系统中用户通常需要有这样一些信息:姓名、电话、密码、描述等。如下图所示,对于 person,通过 cn 和 sn 设置用户的名和姓,这是必须设置的,而其他属性则是可选的。

下面列出部分常用 objectClass 要求必设的属性。
● account:userid。
● organization:o。
● person:cn 和 sn。
● organizationalPerson:与 person 相同。
● organizationalRole:cn。
● organizationUnit:ou。
● posixGroup:cn、gidNumber。
● posixAccount:cn、gidNumber、homeDirectory、uid、uidNumber。

本作品采用《CC 协议》,转载必须注明作者和本文链接
你还差得远呐!