LDAP基础安装与简单入门使用.md

2022-09-29 16:05:07 浏览数 (1)

0x00 前言简述

由于其公司内部都内部各种运维系统等,当每个新员工入职就需要一个挨一个的登录到每个系统的后台给新员工开通账号,设置密码,然后员工离职还得去到每个系统后台去关闭账号,想想多浪费时间那么能不能维护一套账号,对所有系统生效呢? 当然有那就是LDAP

在深入学习LDAP协议之前我们需要了解什么是目录服务?

1.描述:目录服务是一个特殊的数据库,用来保存描述性的、基于属性的详细信息,支持过滤功能。如:人员组织管理,电话簿,地址簿。 2.特点:是动态的,灵活的,易扩展的。 3.目录是一个为查询、浏览和搜索而优化的数据库,它成树状结构组织数据,类似文件目录一样。

什么是LDAP?

答:维基百科:它是一个轻型目录访问协议(英文:Lightweight Directory Access Protocol,缩写:LDAP,/ˈɛldæp/)是一个开放的,中立的,工业标准的应用协议,通过IP协议提供访问控制和维护分布式信息的目录信息。LDAP目录服务是由目录数据库和一套访问协议组成的系统,它是基于X.500标准的轻量级目录访问协议有时被称为X.500-lite。也是IETF下的一项标准,目前最新的RFC为 RFC4510 ;

比如 DNS 协议便是一种最被广泛使用的目录服务

LDAP有什么用?

答:构建一个统一的账号管理、身份验证平台,实现SSO单点登录机制,即用户可以在多个应用服务系统中使用同一个密码,通常用于公司内部网站的登录以及域内机器登陆管理;

特点:

  • 使用轻量级目录访问协议(LDAP)构建集中的身份验证系统可以减少管理成本,增强安全性,避免数据复制的问题,并提高数据的一致性。
  • 目录数据库和关系数据库不同,它有优异的读性能,但写性能差,并且没有事务处理、回滚等复杂功能,不适于存储修改频繁的数据;

LDAP协议版本:

  • LDAPv2
  • LDAPv3

为什么要使用LDAP?

答:LDAP是开放的Internet标准,支持跨平台的Internet协议,在业界中得到广泛认可的,并且市场上或者开源社区上的大多产品都加入了对LDAP的支持,因此对于这类系统,不需单独定制,只需要通过LDAP做简单的配置就可以与服务器做认证交互;>简单粗暴,可以大大降低重复开发和对接的成本

LDAP目录与普通数据库有何异同?

1.主要不同之处在于数据的组织方式,它是一种有层次的、树形结构。所有条目的属性的定义是对象类object class的组成部分,并组成在一起构成schema;那些在组织内代表个人的schema被命名为white pages schema。 2.数据库内的每个条目都与若干对象类联系,而这些对象类决定了一个属性是否为可选和它保存哪些类型的信息。属性的名字一般是一个易于记忆的字符串,例如用cn为通用名(common name)命名,而”mail”代表e-mail地址。 例如mail属性包含值“user@example.com”。

主要产品

简单了解下基于 LDAP 协议的产品有一下:

厂商

产品

简介

Opensource

Opensource

OpenLDAP 开源的项目,速度很快,但是非主流应用,但是在社区的影响下逐渐成熟。

Microsoft

Microsoft Active Directory

基于win系统用户在域控中使用,对大数据量处理速度一般,但维护容易,生态圈大,管理相对简单。

IBM

IBM Directory Server

基于DB2 的的数据库,速度一般。

Novell

Novell Directory Server

基于文本数据库的存储,速度快, 不常用到。

Oracle

Oracle Internet Directory

简称OID是Oracle 用来集中存储和管理网络服务名称的解决方案,用于查询和修改任何类似目录的实体;

Apache

Apache Directory Server

广泛使用在Apache基金会下面所属软件中比如Apache http,进行目录的索引以及展示;如OpenLDAP Apache HTTP Server使用代理服务器(通过模块mod_proxy)支持LDAP。

基本模型

每一个系统、协议都会有属于自己的模型,当然LDAP也不例外;

在了解LDAP的基本模型之前我们需要先了解几个LDAP的目录树概念:

  1. 目录树:在一个目录服务系统中,整个目录信息集可以表示为一个目录信息树,树中的一个节点称之为条目(Entry),条目包含了该节点的属性及属性值。
  2. 条目:由属性(attribute)的一个聚集组成每个条目就是一条记录,并由一个唯一性的名字引用,即专有名称distinguished name,DN),可描述一个层次结构,这个结构可以反映一个政治、地理或者组织的范畴。
  3. 对象类:与某个实体类型对应的一组属性,对象类是可以继承的,这样父类的必须属性也会被继承下来。
  4. 属性:描述条目的某个方面的信息,一个属性由一个属性类型和一个或多个属性值组成,属性有必须属性和非必须属性。补充:属性取值依赖于其类型,并且LDAPv3中一般非二进制值都遵从UTF-8字符串语法。

关键字&术语说明: Entry (or object) 条目(或对象):LDAP中的每个单元都认为是条目。

术语

说明

Directory

目录,用于存放信息的单元

Entry

条目,实体LDAP的基本信息单元

LDIF

全称:LDAP Interchange Format , 在RFC2849中定义的标准,用于规范LDAP的配置和目录内容等详细信息的保存,后续的例子中将会较多地使用LDIF进行增删改查的操作。

关键字

全称

简述

DN

Distinguished Name

专有名称 , DN值:”uid=admin,ou=people,dc=weiyigeek,dc=top”, 一条记录的位置(全局唯一)

DC

Domain Component -

域名组件 , 域名的部分,其格式是将完整的域名分成几部分,如域名为example.com变成dc=example,dc=com(一条记录的所属位置)

CN

Common Name

公共名称 , 如“Admin(一条记录的名称)”或者人名或对象名称

OU

Organization Unit

组织单位,组织单位可以包含其他各种对象(包括其他组织单元),如“oa组”(一条记录的所属组织)

UID

User Id

用户ID username(一条记录的ID)

SN

Surname

姓,如“文”

RDN

Relative dn

相对辨别名,类似于文件系统中的相对路径,它是与目录树结构无关的部分,如“uid=admin”或“cn=ADMIN”

参照 YAPI 中的 LDAP 配置

代码语言:javascript复制

{
"ldapLogin": {
      "enable": true,
      "server": "ldap://l-ldapt1.ops.dev.weiyigeek.top",
      "baseDn": "CN=Admin,CN=Users,DC=weiyigeek,DC=top",
      "bindPassword": "password123",
      "searchDn": "OU=UserContainer,DC=weiyigeek,DC=top",
      "searchStandard": "mail"
   }
}

(1) 信息模型

描述:在LDAP中的信息以树状方式组织,在树状信息中的基本数据单元是条目,而且每个条目由属性构成,属性中存储由属性值;

代码语言:javascript复制
   条目			   属性
-------------    --------------
|	条目1   |    |    类型	   |
|条目2  条目3|--> | 值1 .... 值n|
-------------    --------------

(2) 命名模型

描述:LDAP中的命名模型即LDAP中的条目定位方式,在LDAP中每个条目均有自己的DN 是该条目在整个树中的唯一名称标识;

LDAP树形结构的构成方式而一般有两种方式:体现到LDIF的详细信息

  • 传统方式:聚焦于国别以及地理信息为上层构成,然后按照地理信息进行继续下行,最后精确到人的姓名以及住址;
  • 互联网域名方式: 上层构成直接使用域名,能结合DNS相关的技术;
代码语言:javascript复制
#传统方式
  c=CN
    |
  st=ChongQing
    |
    o = YongChuan (The Organisation)
  /                  
ou=YoungChuanGroup1  ou=YoungChuanGroup2 (Oraganisation Unit)
|
cn=WeiyiGeek

#互联网域名方式
         dc=top
            |
      dc=WeiyiGeek
       /          
 ou=people     ou=groups
    /
uid=admin

(3) 功能模型

描述:在LDAP中共有四类10种操作:

  • 查询类操作,如搜索、比较;
  • 更新类操作,如添加条目,删除条目,修改条目以及修改条目名
  • 认证类操作,如绑定,解绑
  • 其它操作,如放弃和扩展操作(除了扩展操作,另外9种是LDAO的标准操作,扩展操作是LDAP中为了增加新的功能,而提供的一种标准扩展框架,当前已经成为LDAP标准的扩展操作,有修改密码和startTLS扩展,在新的RFC标准和草案中正在增加一些新的扩展操作,不同的LDAP厂商也均定义了自己的扩展操作)

(4) 安全模型

描述:LDAP中的安全模型主要通过身份认证、安全通道和访问控制来进行实现;

应用场景

描述:由于LDAP主要运用于统一身份认证,而其主要是改变原有的认证策略,使需要认证的软件都通过LDAP进行认证,在统一身份认证之后,用户的所有信息都存储在AD Server中。终端用户在需要使用公司内部服务的时候,都需要通过AD服务器的认证。

WeiyiGeek.LDAP身份认证

那么程序中是如何访问的呢?

  1. 连接到LDAP服务器;
  2. 绑定到LDAP服务器;
  3. 在LDAP服务器上执行所需的任何操作;
  4. 释放LDAP服务器的连接;

我们以PHP脚本作为例子如下:

代码语言:javascript复制
$ldapconn = ldap_connect("10.10.84.78")
$ldapbind = ldap_bind($ldapconn, 'username', $ldappass);
$searchRows= ldap_search($ldapconn, $basedn, "(cn=*)");
$searchResult = ldap_get_entries($ldapconn, $searchRows);
ldap_close($ldapconn);

0x01 环境安装

描述:在上文中我们对LDAP产品进行列举,在厂商得持续开发迭代下提供LDAP服务的软件有很多商业上获得成功的,其中以MS的AD和Redhat的NDS(Netscape directory server)使用最为广泛,而开源领域则是OpenLdap(全文实验也是基于此版本);

什么是OpenLDAP? OpenLDAP是轻型目录访问协议(Lightweight Directory Access Protocol,LDAP)的自由和开源的实现,在其OpenLDAP许可证下发行,并已经被包含在众多流行的Linux发行版中。 官网:http://www.openldap.org

它主要包括下述4个部分:

  • slapd - 独立LDAP守护服务
  • slurpd - 独立的LDAP更新复制守护服务
  • 实现LDAP协议的库
  • 工具软件和示例客户端

端口说明:

端口号

备注

389

未加SSL缺省端口(明文数据传输)

689

SSL加密端口

636

加密监听端口(加密数据传输)

基于 yum 安装

环境准备:

代码语言:javascript复制
#SeLinux设为disabled
setenforce 0
sed -i "s/SELINUX=enforcing/SELINUX=disabled/g" /etc/selinux/config 

#同步系统时间
ntpdate time.nist.gov

#Firewalld防火墙设置

#镜像源设置
mv /etc/yum.repos.d/CentOS-Base.repo{,.bak}
wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo
yum clean all #清除缓存
yum makecache #创建缓存

#安装OpenLDAP的相关(compat-openldap包与主从有很大的关系)
yum -y install openldap openldap-clients openldap-servers openldap-servers-sql openldap-devel migrationtools
yum -y install compat-openldap

#通过查看安装了哪些包
rpm -qa |grep openldap

#查看OpenLDAP版本
slapd -VV

#初始化OpenLDAP的配置
cp /usr/share/openldap-servers/DB_CONFIG.example /var/lib/ldap/DB_CONFIG   # 数据库文件
cp /usr/share/openldap-servers/slapd.conf.obsolete /etc/openldap/slapd.conf # 创建slapd.conf


#salpd.conf 配置修改:[修改或添加-如下图所示]
#把配置文件中:dc=my-domain,dc=com  修改成自己的域名; dc=58jb,dc=org ;cn=Manager就是管理员账号;
database    bdb
suffix      "dc=weiyigeek,dc=com,dc=cn"  #基础DN
checkpoint  1024 15
rootdn      "cn=admin,dc=weiyigeek,dc=com,dc=cn" #管理员admin DN
loglevel Stats         #增加一个日志记录
cachesize 1000      #缓存大小


#设置OpenLDAP管理员密码,拷贝这个到/etc/openldap/slapd.conf里,指定rootpw属性必须顶格写,而且与后面的密码文件用Tab键隔开,并修改对应序列。
slappasswd -s 123456
# {SSHA}JTudNsYrtbsksTdjxe4bFSwbrt1cF LD

注意:其中cn=root中的root表示OpenLDAP管理员的用户名,而olcRootPW表示OpenLDAP管理员的密码。

WeiyiGeek.salpd.conf

修改配置文件完成后进行验证配置文件以及权限设置

代码语言:javascript复制
#先检测/etc/openldap/slapd.conf是否有错误
slaptest -f /etc/openldap/slapd.conf 

#授权数据文件,更改文件的所属
chown -R ldap:ldap /var/lib/ldap/

#重启slapd生成配置文件,接着回到检测/etc/openldap/slapd.conf是否有错误
/etc/init.d/slapd restart
slaptest -f /etc/openldap/slapd.conf 

#如果没有问题就重新生成配置文件的配置信息:
rm - rf /etc/openldap/slapd.d/*                                     # 先删除最先的配置文件生成的信息 
slaptest -f /etc/openldap/slapd.conf -F /etc/openldap/slapd.d/       # 重新生成文件

#查看是否生成的是自己修改的配置文件信息,修改文件在/etc/openldap/slapd.d/cn=config/olcDatabase={1}mdb.ldif
#重启到这里为止,OpenLDAP服务端基本上完成了
/etc/init.d/slapd restart

#检查ldap的连接测试,输入密码后返回信息:No such object (32) 表示成功!
ldapsearch -LLL -W -x -H ldap://58jb.org -D "cn=admin,dc=weiyigeek,dc=com,dc=cn" -b "dc=weiyigeek,dc=com,dc=cn" "(uid=*)"

WeiyiGeek.

注意:每次修改了配置文件,所有得重新生成配置文件的信息

参考文档:

  • https://blog.51cto.com/skypegnu1/1939302
  • https://www.cnblogs.com/dmjx/p/9071068.html
  • https://www.58jb.com/html/120.html
基于 Docker 安装

描述:由于openldap的osixia的镜像所内置的缺省的dc即LDAP结构采用互联网域名方式; Docker镜像:https://github.com/osixia/docker-openldap

建立数据持久化目录:

代码语言:javascript复制
mkdir -p /opt/OpenLDAP/{config,database}

docker-compose.yml (v1.3.0) 安装OpenLDAP版本:Jul 30 2019 16:24:19

代码语言:javascript复制
#OpenLDAP 环境部署
#mkdir -p /opt/OpenLDAP/{config,database}
version: '3.1'
services:
  openldap-service:
    image: osixia/openldap
    container_name: openldap
    restart: always
    environment:
      LDAP_DOMAIN: weiyigeek.top
      LDAP_ORGANISATION: weiyigeek
      LDAP_ADMIN_PASSWORD: weiyigeek
      LDAP_TLS: 'false'
    volumes:
      - "/opt/OpenLDAP/database:/var/lib/ldap"
      - "/opt/OpenLDAP/config:/etc/ldap/slapd.d"
    ports:
      - 389:389
      - 689:689
    networks:
      - opt_default

  phpldapadmin-service:
    image: osixia/phpldapadmin
    container_name: phpldapadmin
    restart: always
    environment:
      PHPLDAPADMIN_LDAP_HOSTS: openldap-service
      PHPLDAPADMIN_HTTPS: 'false'
    ports:
      - 6443:443
      - 6080:80
    networks:
      - opt_default
    links:
      - openldap-service
    depends_on:
      - openldap-service

networks:
  opt_default:
    external: true

环境变量说明:

代码语言:javascript复制
默认登录用户名:admin
配置LDAP组织者:--env LDAP_ORGANISATION="weiyigeek"
配置LDAP域:--env LDAP_DOMAIN="weiyigeek.top"
配置LDAP密码:--env LDAP_ADMIN_PASSWORD="weiyigeek"
配置管理LDAP主机地址: --env PHPLDAPADMIN_LDAP_HOSTS=172.17.0.6
配置不开启HTTPS:--env PHPLDAPADMIN_HTTPS=false(默认是true)

拉取并运行镜像:

代码语言:javascript复制
docker-compose config  #验证yaml文件
docker-compose up -d   #拉取和后台运行容器
#如容器间不能正常通信请查看防火墙是否allow;

容器内部查询:

代码语言:javascript复制
#Docker安装版本查询,如果您是基于CentOS系列构建得请采用rpm -qa
root@4a1a157c5e70:/$apt list | grep "slapd"
slapd/now 2.4.48 dfsg-1~bpo10 1 amd64 [installed,local]

root@4a1a157c5e70:/$slapd -VV
@($) $OpenLDAP: slapd  (Jul 30 2019 16:24:19) $
        Debian OpenLDAP Maintainers <pkg-openldap-devel@lists.alioth.debian.org>

root@4a1a157c5e70:/$ldapsearch -VV
ldapsearch: @(#) $OpenLDAP: ldapsearch  (Jul 30 2019 16:24:19) $
        Debian OpenLDAP Maintainers <pkg-openldap-devel@lists.alioth.debian.org>
        (LDAP library: OpenLDAP 20448)

#使用slapcat来查看相关的用户信息,缺省方式下已经有cn=admin的用户信息(貌似不需要认证直接显示用户密码)
root@4a1a157c5e70:/$slapcat -v
# id=00000001
dn: dc=WeiyiGeek,dc=com,dc=cn
objectClass: top
objectClass: dcObject
objectClass: organization
o: WeiyiGeek
dc: WeiyiGeek
structuralObjectClass: organization
entryUUID: bbbb1564-0d95-103a-95a6-a917d679da5b
creatorsName: cn=admin,dc=WeiyiGeek,dc=com,dc=cn
createTimestamp: 20200408033527Z
entryCSN: 20200408033527.510607Z#000000#000#000000
modifiersName: cn=admin,dc=WeiyiGeek,dc=com,dc=cn
modifyTimestamp: 20200408033527Z

0x02 LDAP配置&命令

描述:Yum安装ldap后的配置文件说明:

代码语言:javascript复制
#OpenLDAP相关配置文件(主配置文件,管理员dn,密码,日志配置,权限等设置)
/etc/openldap/slapd.conf
/etc/openldap/slapd.d/*                                     # 这下面是slapd.conf配置信息生成的文件,每修改一次配置信息,这里的东西就要重新生成
/etc/openldap/schema/*                                      # OpenLDAP的schema存放的地方
/var/lib/ldap/*                                             # OpenLDAP的数据文件
/usr/share/openldap-servers/slapd.conf.obsolete             # 模板配置文件
/usr/share/openldap-servers/DB_CONFIG.example               # 模板数据库配置文件

openLDAP的打开日志信息:

代码语言:javascript复制
#设置LDAP日志级别为-2记录Info信息
$vim /etc/openldap/slapd.conf 
loglevel -2

#重新生成配置文件的信息
slaptest -f /etc/openldap/slapd.conf -F /etc/openldap/slapd.d/
chown -R ldap.ldap /etc/openldap/slapd.d/

#创建日志文件目录并修改属组
mkdir /var/log/slapd
chmod 755 /var/log/slapd/
chown ldap.ldap /var/log/slapd/

#在/etc/rsyslog.conf文件中加入  
local4.*    /var/log/slapd/slapd.log

#重启slapd以及rsyslog服务
/etc/init.d/slapd restart
/etc/init.d/rsyslog restart

#查看日志信息
tailf /var/log/slapd/slapd.log

注意事项:

从OpenLDAP2.4.23版本开始所有配置数据都保存在 /etc/openldap/slapd.d/ 中,建议不再使用slapd.conf作为配置文件。

线上ACL控制配置解析希望能达到的效果是:1.管理员能够有全部权限,包含新建用户,修改用户属性及用户密码等|2.普通用户只能修改自己的密码,别的权限都没有;

代码语言:javascript复制
# access to attrs=userPassword通过属性找到访问范围密码,
# 超级管理员也就是我们ldap配置文件里写的rootdn:"cn=admin,dc=weiyigeek,dc=top"有写(write)权限;
# 由于管理员可能不止一个,我创建了个管理员组"ou=Admin,dc=weiyigeek,dc=top"把管理员统一都放到这个组下,管理员组下的所有用户(dn.children)有写权限;
# 匿名用户(anonymous)要通过验证(auth);
# 自己(self)有对自己密码的写(write)权限,其他人(*)都没有权限(none).
access to attrs=userPassword,shadowLastChange
        by dn="cn=admin,dc=weiyigeek,dc=top" write
        by dn.children="ou=Admin,dc=weiyigeek,dc=top" write
        by anonymous auth
        by self write
        by * none

# access to * 所有其他属性,
# 超级管理员rootdn:"cn=admin,dc=weiyigeek,dc=top"有写(write)权限;
# 管理员"ou=Admin,dc=weiyigeek,dc=top"成员有写(write)权限;
# 其他人(*)只有读(read)权限
access to *
        by dn="cn=admin,dc=weiyigeek,dc=top" write
        by dn.children="ou=Admin,dc=weiyigeek,dc=top" write
        by * read
slapd 命令

基础示例:

代码语言:javascript复制
#ldap版本信息
$slapd -VV

#确认配置文件信息是否正确
$slaptest
# config file testing succeeded

#查看相关的用户信息(缺省方式 cn=admin)
$slapcat -v
$slapcat -n 0  #查看不同的数据库id数据{0,1}
ldapsearch 命令

描述:LDAP一般用于SSO的单点登录,所以其他机器能够连接进行验证是最基础的,客户端安装openldap-client包进行登录LDAP并进行查询使用;

代码语言:javascript复制
yum install openldap-clients

基础语法:

代码语言:javascript复制
-x: 采用简单认证
-H: ldap协议://主机:端口不能与-h和-p同时使用
-b: 指定的查询的DN条目对象的属性
-D: 指定的baseDN专有名称
-w: 简单认证方式的密码(credentials)绑定DN的密码,与-W二者选一
-h: LDAP服务器IP或者可解析的hostname,与-p可结合使用,不能与-H同时使用
-p: LDAP服务器端口
-W 	不输入密码,会交互式的提示用户输入密码,与-w二者选一
-f 	指定输入条件,在RFC 4515中有更详细的说明
-c 	出错后忽略当前错误继续执行,缺省情况下遇到错误即终止
-n 	模拟操作但并不实际执行,用于验证,常与-v一同使用进行问题定位
-v 	显示详细信息
-d 	显示debug信息,可设定级别
-s 	指定搜索范围, 可选值:base|one|sub|children

filter 过滤条件符:

WeiyiGeek.

基础示例:

代码语言:javascript复制
#(1)查询得到的实际上是LDIF文件格式中的内容;
$ldapsearch -x -H ldap://localhost:389 -b "dc=WeiyiGeek,dc=com,dc=cn" -D "cn=admin,dc=WeiyiGeek,dc=com,dc=cn" -w WeiyiGeek
# extended LDIF
# LDAPv3
# base <dc=WeiyiGeek,dc=com,dc=cn> with scope subtree
# filter: (objectclass=*)
# requesting: ALL
# weiyigeek.top
dn: dc=WeiyiGeek,dc=com,dc=cn
objectClass: top
objectClass: dcObject
objectClass: organization
o: WeiyiGeek
dc: WeiyiGeek

# WeiyiGeek, Development, weiyigeek.top
dn: cn=WeiyiGeek,ou=Development,dc=WeiyiGeek,dc=com,dc=cn
sn: WeiyiGeek
cn:: IFdlaXlpR2Vlaw==
uid: weiyigeek
userPassword:: e01ENX00U****StWdUJYOGcrSVBnPT0=
uidNumber: 1000
gidNumber: 500
homeDirectory: /home/users/weiyigeek
loginShell: /bin/bash
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: top
mail: test@weiyigeek.top
displayName:: 5ZSv5LiA5p6B5a6i


#(2)指定DN进行查询(指定过滤条件) -b SearchDN
$ldapsearch -x -H ldap://localhost:389 -b "ou=Development,dc=WeiyiGeek,dc=com,dc=cn" -D "cn=admin,dc=WeiyiGeek,dc=com,dc=cn" -w WeiyiGeek
$ldapsearch -x -H ldap://localhost:389 -b "cn=Weiyi,ou=Development,dc=WeiyiGeek,dc=com,dc=cn" -D "cn=admin,dc=WeiyiGeek,dc=com,dc=cn" -w WeiyiGeek
# extended LDIF
# LDAPv3
# base <cn=Weiyi,ou=Development,dc=WeiyiGeek,dc=com,dc=cn> with scope subtree
# filter: (objectclass=*)
# requesting: AL
# Weiyi, Development, weiyigeek.top
dn: cn=Weiyi,ou=Development,dc=WeiyiGeek,dc=com,dc=cn
sn: Weiyi
cn: weiyi
uid: weiyi
uidNumber: 1001
gidNumber: 500
homeDirectory: /home/users/weiyi
loginShell: /bin/bash
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: top
mail: weiyi@weiyigeek.top
displayName: WeiyiName
# search result
search: 2
result: 0 Success
# numResponses: 2
# numEntries: 1


#(3)模糊匹配条目可以采用通配符进行查找,同时可以结合>和<以及~匹配查找等方式实现更加快速和便捷地定位。
#常用的方式还可以使用cn=*或者cn=admin这样进行指定信息进行过滤
$ldapsearch -x -H ldap://localhost:389 -b "dc=WeiyiGeek,dc=com,dc=cn" -D "cn=admin,dc=WeiyiGeek,dc=com,dc=cn" -w WeiyiGeek "cn=*"
$ldapsearch -x -H ldap://localhost:389 -b "dc=WeiyiGeek,dc=com,dc=cn" -D "cn=admin,dc=WeiyiGeek,dc=com,dc=cn" -w WeiyiGeek "ou=*"
# Development, weiyigeek.top
dn: ou=Development,dc=WeiyiGeek,dc=com,dc=cn
ou: Development
objectClass: organizationalUnit
objectClass: top
# People, weiyigeek.top
dn: ou=People,dc=WeiyiGeek,dc=com,dc=cn
objectClass: top
objectClass: organizationalUnit
ou: People
# search result
search: 2
result: 0 Success
# numResponses: 3
# numEntries: 2


#(4) 指定返回信息(类似与在SQL中写Select并且可以采用指定规则排序)
#我们只返回创建的uid的 mail/uid/title/cn/sn
$ldapsearch -x -H ldap://localhost:389 -b "dc=WeiyiGeek,dc=com,dc=cn" -D "cn=admin,dc=WeiyiGeek,dc=com,dc=cn" -w WeiyiGeek "cn=Weiyi" sn cn uid uidNumber mail
# Weiyi, Development, weiyigeek.top
dn: cn=Weiyi,ou=Development,dc=WeiyiGeek,dc=com,dc=cn
sn: Weiyi
cn: weiyi
uid: weiyi
uidNumber: 1001
mail: weiyi@weiyigeek.top


#(5) 服务端下查看openldap服务下的dn配置
$ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -b cn=config dn
dn: cn=config
dn: cn=module{0},cn=config
dn: cn=schema,cn=config


#(6) 指定过滤条件查询
$ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -b "dc=WeiyiGeek,dc=com,dc=cn" "(ou=*)"  #服务端
$ldapsearch -LLL -x -H ldap://127.0.0.1:389/ -D "cn=admin,dc=WeiyiGeek,dc=com,dc=cn" -b "dc=WeiyiGeek,dc=com,dc=cn" "(ou=*)" -w WeiyiGeek #Client
dn: ou=Group,dc=WeiyiGeek,dc=com,dc=cn
ou: Group
objectClass: top
objectClass: organizationalUnit

dn: ou=People,dc=WeiyiGeek,dc=com,dc=cn
ou: People
objectClass: organizationalUnit
objectClass: top

补充事项:

1.查询到的是经过Base64编码的字符串,通过解码得到SSHA加密后密匙

代码语言:javascript复制
12:20:25.644 atob("e1NTSEF9TE5GaU0rZVZXNGlR43FDdzQ5UzRMNjRlb2xjMjY5OU4=")
12:20:25.745 "{SSHA}LNFiM8eVW4iQGq2w49S4L74eolc2699N"

2.过滤条件实例

  • 下列过滤器将搜索包含一个或多个 manager 属性值的条目称为存在搜索: manager=*
  • 下列过滤器将搜索包含通用名 WeiyiGeek 的条目。这也称为等价搜索:cn=WeiyiGeek
  • 下列过滤器返回所有不包含通用名 WeiyiGeek 的条目:(!(cn=WeiyiGeek))
  • 下列过滤器返回的所有条目中都有包含子字符串 X.500 的说明属性:description=X.500
  • 下列过滤器返回所有组织单元为 Marketing 且说明字段中不包含子字符串 X.500 的条目: (&(ou=Marketing)(!(description=X.500)))
  • 下列过滤器返回所有组织单元为 Marketing 且 manager 为 WeiyiGeek 或 Cindy Zwaska 的条目: (&(ou=Marketing)(|(manager=cn=WeiyiGeek,ou=Marketing,dc=siroe,dc=com)(manager=cn=Cindy Zwaska,ou=Marketing,dc=siroe,dc=com)))
  • 下列过滤器返回所有不代表人员的条目: (!(objectClass=person))
  • 下列过滤器返回所有不代表人员且通用名近似于 printer3b 的条目:(&(!(objectClass=person))(cn~=printer3b))
  • 下列过滤器返回对象类为inetOrgPerson或者为groupOfUniqueNames的条目:(|(objectClass=inetOrgPerson)(objectClass=groupOfUniqueNames))
ldapadd 命令
ldapmodify 命令

描述:该命令用于进行数据添加,实际上ldapadd只是采用了一个软链接指向ldapmodify,所以此处我们统一进行说明;

命令参数:

代码语言:javascript复制
Option 	说明
-H 	ldapuri,格式为ldap://机器名或者IP:端口号,不能与-h和-p同时使用
-h 	LDAP服务器IP或者可解析的hostname,与-p可结合使用,不能与-H同时使用
-p 	LDAP服务器端口号,与-h可结合使用,不能与-H同时使用
-x 	使用简单认证方式
-D 	所绑定的服务器的DN
-w 	绑定DN的密码,与-W二者选一
-W 	不输入密码,会交互式的提示用户输入密码,与-w二者选一
-f 	指定ldif文件作为输入
-a 	添加新的entry,ldapadd缺省使用,ldapmodify 可指定以达到同样作用
-c 	出错后忽略当前错误继续执行,缺省情况下遇到错误即终止
-n 	模拟操作但并不实际执行,用于验证,常与-v一同使用进行问题定位
-v 	显示详细信息
-d 	显示debug信息,可设定级别
-e 	设置客户端证书
-E 	设置客户端私钥

测试添加的LDIF文件内容:

代码语言:javascript复制
cat > weiyigeek.ldif <<END
#添加Organisation Unit
dn: ou=People,dc=WeiyiGeek,dc=com,dc=cn
changetype: add
objectclass: top
objectclass: organizationalUnit
ou: People

#添加用户
dn: cn=Weiyi,ou=Development,dc=WeiyiGeek,dc=com,dc=cn
changetype: add
sn: Weiyi
cn: weiyi
uid: weiyi
uidNumber: 1001
gidNumber: 500
homeDirectory: /home/users/weiyi
loginShell: /bin/bash
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: top
mail: weiyi@weiyigeek.top
displayName: WeiyiName


END

#ldapmodify ldif文件添加、修改(添加W字段)
changetype: add
changetype: modify

cat > Demo1.ldif<<END
#修改字段
dn: cn=Weiyi,ou=Development,dc=WeiyiGeek,dc=com,dc=cn
changetype: modify
replace: mail
mail: WeiyiGeek@weiyi.top

#添加字段
dn: cn=Weiyi,ou=Development,dc=WeiyiGeek,dc=com,dc=cn
changetype: modify
add: description
description: Test User

#删除字段
dn: cn=Weiyi,ou=Development,dc=WeiyiGeek,dc=com,dc=cn
changetype: modify
delete: description

#移动条目&修改UID
dn: cn=Weiyi,ou=Development,dc=WeiyiGeek,dc=com,dc=cn
changetype: modrdn
newrdn: uid=WeiyiTest
deleteoldrdn: 0
newsuperior: ou=People,dc=WeiyiGeek,dc=com,dc=cn

#删除条目(未实现成功)
dn: uid=WeiyiTest,ou=People,dc=WeiyiGeek,dc=com,dc=cn
changetype: delete
END

cat > changePasswd.ldif<<END
#添加密码
dn: uid=WeiyiTest,ou=People,dc=WeiyiGeek,dc=com,dc=cn
changetype: modify
add: userPassword
userPassword: 123456

#修改密码
dn: uid=WeiyiTest,ou=People,dc=WeiyiGeek,dc=com,dc=cn
changetype: modify
replace: userPassword
userPassword: 123456
END

基础实例:

代码语言:javascript复制
#指定ldif文件进行添加用户(ldapadd)
$ldapadd -H ldap://127.0.0.1:389 -D "cn=admin,dc=WeiyiGeek,dc=com,dc=cn" -w WeiyiGeek -f weiyigeek.ldif
adding new entry "cn=Weiyi,ou=Development,dc=WeiyiGeek,dc=com,dc=cn"

#指定ldif文件进行添加用户 (ldapmodify:方式1,采用-a参数)
$ldapmodify -a -x -H ldap://127.0.0.1:389 -D "cn=admin,dc=WeiyiGeek,dc=com,dc=cn" -w WeiyiGeek -f weiyigeek.ldif
adding new entry "cn=Weiyi,ou=Development,dc=WeiyiGeek,dc=com,dc=cn"

#指定ldif文件进行添加用户、修改 (ldapmodify:方式2需要在ldif文件中加入 changetype: add )
$ldapmodify -x -H ldap://127.0.0.1:389 -D "cn=admin,dc=WeiyiGeek,dc=com,dc=cn" -w WeiyiGeek -f weiyigeek.ldif
adding new entry "ou=People,dc=WeiyiGeek,dc=com,dc=cn"
adding new entry "cn=Weiyi,ou=Development,dc=WeiyiGeek,dc=com,dc=cn"

#指定ldif文件进行修改、增加字段、删除字段、移动条目、删除条目
$ldapmodify -a -x -H ldap://127.0.0.1:389 -D "cn=admin,dc=WeiyiGeek,dc=com,dc=cn" -w WeiyiGeek -f Demo1.ldif
modifying entry "cn=Weiyi,ou=Development,dc=WeiyiGeek,dc=com,dc=cn"
modifying entry "cn=Weiyi,ou=Development,dc=WeiyiGeek,dc=com,dc=cn"
modifying entry "cn=Weiyi,ou=Development,dc=WeiyiGeek,dc=com,dc=cn"
modifying rdn of entry "cn=Weiyi,ou=Development,dc=WeiyiGeek,dc=com,dc=cn"
deleting entry "cn=WeiyiTest,ou=People,dc=WeiyiGeek,dc=com,dc=cn"

Question:中的ldapadd替换称ldapmodify是否能够完全一致的动作? Ask:不行,其报错信息:ldapmodify: modify operation type is missing at line 2, entry "cn=Weiyi,ou=Development,dc=WeiyiGeek,dc=com,dc=cn",需要加上-a参数即可;

注意事项:

  • 当部门和人员的信息还未进行关联,当然最简单的方式是先创建部门,然后在人员信息中添加相关内容即可
  • 添加部门关键信息则为organisationalUnit,添加用户时候关键的objectclass是inetOrgPerson
ldapdelete 命令

基础实例:

代码语言:javascript复制
$ldapdelete -x -h 127.0.0.1 -p 389 -D "cn=admin,dc=WeiyiGeek,dc=com,dc=cn" -w WeiyiGeek "cn=Weiyi,ou=Development,dc=WeiyiGeek,dc=com,dc=cn"
slappasswd 命令
ldappasswd 命令

描述:LDAP有三种方式可以进行修改密码即

  • slappasswd命令: 管理员密码修改
  • ldappasswd命令: 用户密码修改
  • ldapmodify命令结合ldif文件

ldappasswd 参数:

代码语言:javascript复制
-H 	ldapuri,格式为ldap://机器名或者IP:端口号,不能与-h和-p同时使用
-h 	LDAP服务器IP或者可解析的hostname,与-p可结合使用,不能与-H同时使用
-p 	LDAP服务器端口号,与-h可结合使用,不能与-H同时使用
-x 	使用简单认证方式
-D 	所绑定的服务器的DN
-w 	绑定DN的密码,与-W二者选一
-W 	不输入密码,会交互式的提示用户输入密码,与-w二者选一
-n 	模拟操作但并不实际执行,用于验证,常与-v一同使用进行问题定位
-v 	显示详细信息
-d 	显示debug信息,可设定级别
-S 	交互式进行密码的提示和输入以及Re-enter,与-s二者选一
-s 	将指定内容设为密码,与-S二者选一

基础实例:

代码语言:javascript复制
#(1)ldappasswd不指定密码的情况下它会自动生成一个密码(实际上是采用admin用户重置其它用户的密码),并添加到userPassword字段之中,之后可以采用前面ldapsearch进行输入此密码进行验证;
$ldappasswd -x -h 127.0.0.1 -p 389 -D "cn=admin,dc=WeiyiGeek,dc=com,dc=cn" -w WeiyiGeek "uid=WeiyiTest,ou=People,dc=WeiyiGeek,dc=com,dc=cn"
New password: XJbHKyRF

#(2) ldappasswd使用-s指定修改密码,在已知用户密码前提下进行修改现有密码
$ldappasswd -x -h 127.0.0.1 -p 389 -D "uid=WeiyiTest,ou=People,dc=WeiyiGeek,dc=com,dc=cn" -w XJbHKyRF -s newpass123456

0x03 LDAP客户端
LDAPAccountManager

采用 docker 容器部署LDAPAccountManager:

代码语言:javascript复制
docker run -d --restart=always --name ldap-account-manager -p 8081:80 
--link openldap:ldap-host 
--env PHPLDAPADMIN_LDAP_HOSTS=ldap-host 
--env PHPLDAPADMIN_HTTPS=false 
--network=opt_default 
--detach ldapaccountmanager/lam

#擦拭解释
--link这里连接到openldap容器并起了一个别名ldap-host
--env PHPLDAPADMIN_LDAP_HOSTS这里直接通过别名指向openldap容器,这样不需要写死IP地址
--env PHPLDAPADMIN_HTTPS 不使用443协议
--restart=always加入此参数是防止系统重启了容器未启动
--network=已存在的网络 [`docker network ls`可以进行查看,实际与LDAP同一个网络即可]

简单置流程:

  • (1) 我们访问http://192.168.107.245:8081/进行LAM基础配置,首次登录点击configuration LAM configuration,然后选择Edit server profiles;
  • (2) 默认账号密码lam/lam进行认证后才能进行配置Server settings Server settings,Security settings 以及Account Types选项卡中的Users与Groups进行 配置

WeiyiGeek.

  • (3) 返回首页采用安全认证中的配置的DN进行登录LDAP然后进行创建默认的组(Unit),实际你会发现与 PHPLDAPAdmin 操作实际相差不大;

WeiyiGeek.

  • (4) 至此,已经完成docker版的openldap和LDAP Account Manager的安装配置。
PHPLdapAdmin

描述:phpLDAPadmin(也称为PLA)是一个基于Web的LDAP客户端。它为LDAP服务器提供简单,随处可访问的多语言管理。phpLDAPadmin是LDAP专业人员和新手的完美LDAP浏览器。其分层树查看器和高级搜索功能使您可以直观地浏览和管理LDAP目录。由于它是一个Web应用程序,因此该LDAP浏览器可在许多平台上运行,使您可以从任何位置轻松管理LDAP服务器。

官网:http://phpldapadmin.sourceforge.net/wiki/index.php/Main_Page

比如采用Docker进行安装:`如果开启HTTPS,需要配置443端口映射:-p 8443:443,并采用https访问

代码语言:javascript复制
docker run -d --privileged -p 10004:80 --name phpldapadmin --env PHPLDAPADMIN_HTTPS=false --env PHPLDAPADMIN_LDAP_HOSTS=172.17.0.6 --detach osixia/phpldapadmin

基础配置 Step1. 访问phpldapAdmin打开浏览器访问:http://192.168.172.245:6080

代码语言:javascript复制
账号:cn=admin,dc=weiyigeek,dc=top
密码:weiyigeek

WeiyiGeek.

Step2.登录phpLdapAdmin添加组以及ldap账号流程步骤如下:

  • 点击新建实体Create new entry here
  • Templates选择:Generic: Organisational Unit 建立组织单元,输入组织名称然后提交
  • 回到首页,再次创建条目Templates选择:Generic: Posix Group创建组 ,输入组名称然后提交
  • 回到首页,点击创建的OU:Development组织,然后点击 Create a child entry 创建创建一个子条目
  • Templates选择:Generic: User Account - 根据需求进行输入后进行创建Object,然后点击创建的CN进行查看显示内部属性Show internal attributes
  • 提交完成后,点击新增的用户,点击右侧【增加新的属性Add new attribute】,选择属性【Email】添好Email地址点击【Update Object】

Step3.采用创建的账号进行登陆LDAP;

代码语言:javascript复制
DN:cn=WeiyiGeek,ou=Development,dc=weiyigeek,dc=com,dc=cn
密码:123456

WeiyiGeek.

LDAPAdmin

描述:Ldap Admin是一个用于LDAP目录管理的免费Windows LDAP客户端和管理工具。此应用程序允许您在LDAP服务器上浏览,搜索,修改,创建和删除对象。它还支持更复杂的操作,例如目录复制和在远程服务器之间移动,并扩展常用编辑功能以支持特定对象类型(例如组和帐户), 支持多类型系统:Winndows&Linux 官网:http://www.ldapadmin.org/

下载安装LDAP Admin客户端,新增连接如下:

WeiyiGeek.

Apache Directory Studio

描述: 设计用来和各种LDAP服务器进行交互操作,提供了一个使用方便的客户端操作平台。除了Apache DS之外,诸如OpenLdap也可以很好地进行交互,对于不习惯不喜欢命令行方式的用户,Apache Directory Studio也是选择之一。

官方网址:http://directory.apache.org/studio/ 支持OS:跨平台,支持MacOS/Windows/Linux 下载地址:http://directory.apache.org/studio/downloads.html

WeiyiGeek.

导入与导出

  • File->右键 DN 选择 Export 进行导出格式为LDIF
  • File->右键 DN 选择 Import 进行导入格式为LDIF的备份文件,还能通过直接修改ldif文件进行创建entry或者移动条目;

WeiyiGeek.

入坑解决: (1)import的失败请按照如下顺序进行原因确认:

  1. ldif文件的多个entry的格式,包括全角字符等
  2. ldif文件内容是否有缺失,拼写是否有错误
  3. ldif文件的内容依赖的部分,这个只能你自己根据系统的情况自行确认,本身ldap就是一个类似目录层级的方式,比如上层目录没有试图添加下层,自然会出错
  4. ldap的权限设定与配置 一般来说都是既存的数据和ldif文件中的某个entry写错了导致的问题,请首先排除这个方面的问题
Migrationtools

描述:采用migrationtools工具包,实现导入系统账号的相关信息;

代码语言:javascript复制
#下载工具包
yum -y install migrationtools

#修改migrationtools的配置文件
vim /usr/share/migrationtools/migrate_common.ph 
70 # Default DNS domain
71 $DEFAULT_MAIL_DOMAIN = "weiyigeek.top";
72
73 # Default base
74 $DEFAULT_BASE = "dc=WeiyiGeek,dc=com,dc=cn";

#生成基础的数据文件(可以根据需求把不需要的去掉)
/usr/share/migrationtools/migrate_base.pl > base.ldif
$more base.ldif
# dn: dc=WeiyiGeek,dc=com,dc=cn
# dc: WeiyiGeek
# objectClass: top
# objectClass: domain

# dn: ou=Hosts,dc=WeiyiGeek,dc=com,dc=cn
# ou: Hosts
# objectClass: top
# objectClass: organizationalUnit


#把base.ldif导入OpenLDAP的数据文件(-c 强制导入)
$ldapadd -x -c -H ldap://127.0.0.1 -D "cn=admin,dc=WeiyiGeek,dc=com,dc=cn" -w WeiyiGeek -f /root/base.ldif
# adding new entry "dc=WeiyiGeek,dc=com,dc=cn"
# adding new entry "ou=Hosts,dc=WeiyiGeek,dc=com,dc=cn"
# adding new entry "ou=Rpc,dc=WeiyiGeek,dc=com,dc=cn"
# adding new entry "ou=Services,dc=WeiyiGeek,dc=com,dc=cn"
# adding new entry "nisMapName=netgroup.byuser,dc=WeiyiGeek,dc=com,dc=cn"
# adding new entry "ou=Mounts,dc=WeiyiGeek,dc=com,dc=cn"
# adding new entry "ou=Networks,dc=WeiyiGeek,dc=com,dc=cn"
# adding new entry "ou=People,dc=WeiyiGeek,dc=com,dc=cn"
# adding new entry "ou=Group,dc=WeiyiGeek,dc=com,dc=cn"
# adding new entry "ou=Netgroup,dc=WeiyiGeek,dc=com,dc=cn"
# adding new entry "ou=Protocols,dc=WeiyiGeek,dc=com,dc=cn"
# adding new entry "ou=Aliases,dc=WeiyiGeek,dc=com,dc=cn"
# adding new entry "nisMapName=netgroup.byhost,dc=WeiyiGeek,dc=com,dc=cn"

WeiyiGeek.导入系统账号的相关信息

创建两个测试用户及用户组,并修改密码

代码语言:javascript复制
groupadd ldaptest1
groupadd ldaptest2
useradd -g ldaptest1 ldaptest1
useradd -g ldaptest2 ldaptest2
echo 123456 | passwd --stdin ldaptest1
echo password | passwd --stdin ldaptest2

将刚创建的两个用户导入至openldap数据文件

代码语言:javascript复制
grep ldaptest /etc/passwd > users
grep ldaptest /etc/group > groups

$cat users groups
ldaptest1:x:1001:1001::/home/ldaptest1:/bin/bash
ldaptest2:x:1002:1002::/home/ldaptest2:/bin/bash
ldaptest1:x:1001:
ldaptest2:x:1002:

使用migrationtools将两个临时用户生成ldif文件

代码语言:javascript复制
/usr/share/migrationtools/migrate_passwd.pl users > users.ldif
cat users.ldif
# dn: uid=ldaptest1,ou=People,dc=WeiyiGeek,dc=com,dc=cn
# uid: ldaptest1
# cn: ldaptest1
# objectClass: account
# objectClass: posixAccount
# objectClass: top
# objectClass: shadowAccount
# userPassword: {crypt}$6$Rr.3Z1X8$fmyCvoFFaTw74GVO.G3okx765i3K2CUaEK/Uu0cyNc8/Xn2HHM1DelVM3nmgDNsQWEZGpLCNUtp.faXvspiff1
# shadowLastChange: 18363
# shadowMin: 0
# shadowMax: 99999
# shadowWarning: 7
# loginShell: /bin/bash
# uidNumber: 1001
# gidNumber: 1001
# homeDirectory: /home/ldaptest1

/usr/share/migrationtools/migrate_group.pl groups > groups.ldif
cat groups.ldif
# dn: cn=ldaptest1,ou=Group,dc=WeiyiGeek,dc=com,dc=cn
# objectClass: posixGroup
# objectClass: top
# cn: ldaptest1
# userPassword: {crypt}x
# gidNumber: 1001

4.把用户导入至openLDAP的数据文件并查询导入的数据

代码语言:javascript复制
ldapadd -x -H ldap://127.0.0.1 -D "cn=admin,dc=WeiyiGeek,dc=com,dc=cn" -w WeiyiGeek -f users.ldif
# adding new entry "uid=ldaptest1,ou=People,dc=WeiyiGeek,dc=com,dc=cn"
# adding new entry "uid=ldaptest2,ou=People,dc=WeiyiGeek,dc=com,dc=cn"

ldapadd -x -H ldap://127.0.0.1 -D "cn=admin,dc=WeiyiGeek,dc=com,dc=cn" -w WeiyiGeek -f groups.ldif
# adding new entry "cn=ldaptest1,ou=Group,dc=WeiyiGeek,dc=com,dc=cn"
# adding new entry "cn=ldaptest2,ou=Group,dc=WeiyiGeek,dc=com,dc=cn"

WeiyiGeek.

JNDI方式

描述:我们可以使用Java中使用javax.naming可以对Ldap用户信息进行验证,使用这点可以完成SSO之类功能的集成;

简单的基础示例:

代码语言:javascript复制
//登陆与验证LDAP
package top.weiyigeek.other;

import java.util.Enumeration;
import java.util.Hashtable;

import javax.naming.Binding;
import javax.naming.Context;
import javax.naming.NameClassPair;
import javax.naming.NamingEnumeration;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;

/**
 * @Descr 使用Javax.naming验证LDAP登录
 * @author WeiyiGeek
 * javac ldaptest.java
 * java ldaptest
 */
public class LDAPDemo {
    public static void main(String[] args) {
        String IP = "10.10.107.245";
        String PORT = "389";
        String BASEDN = "dc=WeiyiGeek,dc=com,dc=cn";
        String MANAGER = "cn=admin";
        String PASS = "WeiyiGeek";
        String USERNAME = "cn=WeiyiGeek,ou=Development";
        String LDAP_URL = "ldap://" IP ":" PORT "/" BASEDN;
        String[] attrIDs = {"mail","displayName","uidNumber","sn" };
        
        //基于哈希表实现的是线程安全的,能用于多线程环境中
        Hashtable<String, String> tbl = new Hashtable<String, String>();
        
        //JNDI 
        //为初始化上下文选择服务提供者
        tbl.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory");
        //提供初始化上下文需要的信息
        tbl.put(Context.PROVIDER_URL, LDAP_URL);
        tbl.put(Context.SECURITY_AUTHENTICATION, "simple");
        tbl.put(Context.SECURITY_PRINCIPAL, MANAGER "," BASEDN);
        tbl.put(Context.SECURITY_CREDENTIALS, PASS);
        
        System.out.println("--------------Show Env Setting --------------");
        Enumeration<String> keys = tbl.keys();
        while(keys.hasMoreElements()) {
            System.out.println(tbl.get(keys.nextElement()));
        }
        System.out.println("");
        System.out.println("--------------Login Verification LDAP    ---------- ");
        
        
        //上下文对象
        DirContext context = null;
        try {
            //创建初始化上下文
            context = new InitialDirContext(tbl);
            System.out.println("Login Successful! 登录成功");
            System.out.println("Login Verification LDAP n");
            
            //举上下文有两个方法
            System.out.println("-------------- 枚举 查询对象 ---------- ");
            //方式1:
//            NamingEnumeration list = context.list("ou=Development");  //指定对象
//            while (list.hasMore()) {
//                NameClassPair nc = (NameClassPair)list.next();
//                System.out.println(nc);
//            }
            
            //方式2:
            NamingEnumeration<?> bindings = context.listBindings(""); //不指定对象则显示全部
            while (bindings.hasMore()) {
                Binding bd = (Binding)bindings.next();
                System.out.println(bd.getName()   ": "    bd.getNameInNamespace()   ": "   bd.getObject() );
            }
            bindings.close();
            
          //对象属性全部读取
            try {
                Attributes answer = context.getAttributes(USERNAME);
                System.out.println("n-----------------获取 "  USERNAME   " 对象属性-----------------");
                for (NamingEnumeration ae = answer.getAll(); ae.hasMore();) {
                    Attribute attr = (Attribute)ae.next();
                    System.out.print(attr.getID());
                    for (NamingEnumeration e = attr.getAll(); e.hasMore(); System.out.println(" : "   e.next()));
                }
            } catch (Exception e) {
               System.out.println("Read Fail : "   USERNAME   " Object 对象不存在!");
            } 
            
           
          //指定对象属性读取
            try {
                Attributes answer = context.getAttributes(USERNAME, attrIDs);
                System.out.println("n----------------- 获取对象指定属性 -----------------");
                for (NamingEnumeration ae = answer.getAll(); ae.hasMore();) {
                    Attribute attr = (Attribute)ae.next();
                    System.out.print(attr.getID());
                    for (NamingEnumeration e = attr.getAll(); e.hasMore(); System.out.println(" : "   e.next()));
                }
            } catch (Exception e) {
               System.out.println("Read Fail : "   USERNAME   " Object 对象不存在!");
            } 
            
            
        } catch (Exception e) {
            System.out.println("Login failed : "   e.getMessage());
        } finally {
            //销毁context上下文对象
            try {
                if (context != null) {
                    context.close();
                    context = null;
                }
                tbl.clear();
            } catch (Exception e) {
                System.out.println("Exception happened."   e.getMessage());
            }
        }
    }
}

执行结果:

代码语言:javascript复制
--------------Show Env Setting --------------
ldap://10.10.107.245:389/dc=WeiyiGeek,dc=com,dc=cn
com.sun.jndi.ldap.LdapCtxFactory
cn=admin,dc=WeiyiGeek,dc=com,dc=cn
simple
WeiyiGeek

--------------Login Verification LDAP    ---------- 
Login Successful! 登录成功
Login Verification LDAP 

-------------- 枚举 查询对象 ---------- 
cn=User: cn=User,dc=WeiyiGeek,dc=com,dc=cn: com.sun.jndi.ldap.LdapCtx@5e9f23b4
cn=admin: cn=admin,dc=WeiyiGeek,dc=com,dc=cn: com.sun.jndi.ldap.LdapCtx@4783da3f
ou=Development: ou=Development,dc=WeiyiGeek,dc=com,dc=cn: com.sun.jndi.ldap.LdapCtx@378fd1ac

-----------------获取 cn=WeiyiGeek,ou=Development 对象属性-----------------
sn : WeiyiGeek
userPassword : [B@49097b5d
loginShell : /bin/bash
uidNumber : 1000
gidNumber : 500
displayName : 唯一极客
mail : test@weiyigeek.top
objectClass : inetOrgPerson
 : posixAccount
 : top
uid : weiyigeek
cn :  WeiyiGeek
homeDirectory : /home/users/weiyigeek

----------------- 获取对象指定属性 -----------------
mail : test@weiyigeek.top
displayName : 唯一极客
uidNumber : 1000
sn : WeiyiGeek

其它博主的代码:

代码语言:javascript复制
package com.example.ldap;
 
import java.util.Hashtable;
import java.util.Random;
 
import javax.naming.AuthenticationException;  
import javax.naming.Context;  
import javax.naming.NamingEnumeration;  
import javax.naming.NamingException;  
import javax.naming.directory.BasicAttribute;  
import javax.naming.directory.BasicAttributes;  
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;
 
/**
 * @author monkey
 * @date 2019-06-28
 * @desc ldap demo
 */
public class LDAPAuthentication {  
 
    private final String URL = "ldap://10.0.43.206:389/";
    private final String BASEDN = "ou=people,dc=youedata,dc=com";  // 根据自己情况进行修改
    private final String FACTORY = "com.sun.jndi.ldap.LdapCtxFactory";
    private LdapContext ctx = null;  
    private final Control[] connCtls = null;  
    
    private void LDAP_connect() {  
        Hashtable<String, String> env = new Hashtable<String, String>();  
        env.put(Context.INITIAL_CONTEXT_FACTORY, FACTORY);  
        env.put(Context.PROVIDER_URL, URL   BASEDN);  
        env.put(Context.SECURITY_AUTHENTICATION, "simple");  
            
        String root = "cn=admin,dc=youedata,dc=com";  //根据自己情况修改
        env.put(Context.SECURITY_PRINCIPAL, root);   // 管理员  
        env.put(Context.SECURITY_CREDENTIALS, "youedata520");  // 管理员密码
           
        try {  
            ctx = new InitialLdapContext(env, connCtls);  
            System.out.println( "LDAP_connect连接成功" );
               
        } catch (javax.naming.AuthenticationException e) {  
            System.out.println("连接失败:");  
            e.printStackTrace();  
        } catch (Exception e) {  
            System.out.println("连接出错:");  
            e.printStackTrace();  
        }  
           
    }  
  private void closeContext(){  
    if (ctx != null) {  
    try {  
        ctx.close();  
    }  
    catch (NamingException e) {  
        e.printStackTrace();  
    }  
  
}  
  }  
    private String getUserDN(String uid) {  
        String userDN = "";  
        LDAP_connect();  
        try {  
            SearchControls constraints = new SearchControls();  
            constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);  
              
            NamingEnumeration<SearchResult> en = ctx.search("", "uid="   uid, constraints);  
              
            if (en == null || !en.hasMoreElements()) {  
                System.out.println("未找到该用户");  
            }  
            // maybe more than one element  
            while (en != null && en.hasMoreElements()) {  
                Object obj = en.nextElement();  
                if (obj instanceof SearchResult) {  
                    SearchResult si = (SearchResult) obj;  
                    userDN  = si.getName();  
                    userDN  = ","   BASEDN;  
                } else {  
                    System.out.println(obj);  
                }  
            }  
        } catch (Exception e) {  
            System.out.println("查找用户时产生异常。");  
            e.printStackTrace();  
        }  
    
        return userDN;  
    }  
    
    public boolean authenricate(String UID, String password) {  
        boolean valide = false;  
        String userDN = getUserDN(UID);  
    
        try {  
            ctx.addToEnvironment(Context.SECURITY_PRINCIPAL, userDN);  
            ctx.addToEnvironment(Context.SECURITY_CREDENTIALS, password);  
            ctx.reconnect(connCtls);  
            System.out.println(userDN   " 验证通过");  
            valide = true;  
        } catch (AuthenticationException e) {  
            System.out.println(userDN   " 验证失败");  
            System.out.println(e.toString());  
            valide = false;  
        } catch (NamingException e) {  
            System.out.println(userDN   " 验证失败");  
            valide = false;  
        }  
        closeContext();  
        return valide;  
    }  
    private  boolean addUser(String usr, String pwd) {  
        
        try {  
            LDAP_connect();  
            BasicAttributes attrsbu = new BasicAttributes();  
            BasicAttribute objclassSet = new BasicAttribute("objectclass");
            //可以和数据库关联,做一个自增,比如mysql存起来
            String str = new Random().nextInt(10000)   "";
            objclassSet.add("inetOrgPerson");  
            objclassSet.add("top");
            objclassSet.add("posixAccount");
            attrsbu.put(objclassSet);
            attrsbu.put("sn", usr);  
            attrsbu.put("cn", usr);  
            attrsbu.put("uid", usr);  
            //邮箱不能重复,不然账号无法登陆
            attrsbu.put("mail", "450416064@qq.com");
            attrsbu.put("gidNumber", str);
            attrsbu.put("uidNumber", str);
            attrsbu.put("homeDirectory", "/home/account");
 
            attrsbu.put("userPassword", pwd);
            ctx.createSubcontext("uid="  usr , attrsbu);
 
            System.out.println("======================>"   usr   "添加成功");
            return true;  
        } catch (NamingException ex) {  
           ex.printStackTrace();  
        }  
        closeContext();  
        return false;  
    }   
    public static void main(String[] args) {  
        LDAPAuthentication ldap = new LDAPAuthentication();  
          
        ldap.LDAP_connect();  
   
        ldap.addUser("qq1111","123456");
        if(ldap.authenricate("qq1111", "123456") == true){
            System.out.println( "该用户认证成功" );
        }
    }  
}

Java连接LDAP-JNDI参考:

  • https://blog.csdn.net/liumm0000/article/details/7932019
  • https://blog.csdn.net/lj88811498/article/details/94025629

0 人点赞