一、什么是LDAP?
(一)在介绍什么是LDAP之前,我们先来复习一个东西:“什么是目录服务?”
1. 目录服务是一个特殊的数据库,用来保存描述性的、基于属性的详细信息,支持过滤功能。
2. 是动态的,灵活的,易扩展的。
如:人员组织管理,电话簿,地址簿。
(二)了解完目录服务后,我们再来看看LDAP的介绍:
LDAP(Light Directory Access Portocol),它是基于X.500标准的轻量级目录访问协议。
目录是一个为查询、浏览和搜索而优化的数据库,它成树状结构组织数据,类似文件目录一样。
目录数据库和关系数据库不同,它有优异的读性能,但写性能差,并且没有事务处理、回滚等复杂功能,不适于存储修改频繁的数据。所以目录天生是用来查询的,就好象它的名字一样。
LDAP目录服务是由目录数据库和一套访问协议组成的系统。
(三)为什么要使用
LDAP是开放的Internet标准,支持跨平台的Internet协议,在业界中得到广泛认可的,并且市场上或者开源社区上的大多产品都加入了对LDAP的支持,因此对于这类系统,不需单独定制,只需要通过LDAP做简单的配置就可以与服务器做认证交互。“简单粗暴”,可以大大降低重复开发和对接的成本。
(四)相关属性说明
属性名称 | 含义 | 备注 |
---|---|---|
OU | Organizational Unit(组织单元) | 最多可以有四级,每级最长 32 个字符,可以为中文。 |
DC | Domain Component(域名) | dc=xxxx,dc=com |
CN | Common Name(用户名或服务器名) | 最长可以到 80 个字符,可以为中文。 |
O | Organization(组织名称) | 可以 3~64 个字符长度。 |
C | Country(国家名) | 可选,为 2 个字符长度。 |
SN | 姓 | |
UID | 用户ID | |
DN | 唯一标识 | 类似于 Linux 文件系统的绝对路径,每个对象都有一个唯一的名称。 |
二、如何访问LDAP?
Softerra LDAP Browser(LDAP客户端工具) 4.5 官方版
三、java 调用范例
pom.xml
<dependency>
<groupId>org.springframework.ldap</groupId>
<artifactId>spring-ldap-core</artifactId>
<version>2.3.2.RELEASE</version>
</dependency>
/**
* 初始化LdapTemplate
*
* @return
*/
private LdapTemplate getLdapTemplate(String url, String base, String userDn, String password) {
if (template == null) {
try {
LdapContextSource contextSource = new LdapContextSource();
contextSource.setUrl(url);
contextSource.setBase(base);
contextSource.setUserDn(userDn);
contextSource.setPassword(password);
contextSource.setPooled(false);
contextSource.afterPropertiesSet(); // important
template = new LdapTemplate(contextSource);
} catch (Exception e) {
e.printStackTrace();
}
}
return template;
}
/**
* 根据用户uid查询ldap用户信息
*
* @return
*/
private LdapUserInfo searchLdapUerInfo(String uid) {
// String url = "ldap://10.10.31.14:389/";
// String base = "dc=authldap,dc=edu,dc=cn";
// String userDn = "uid=ldap_gh,ou=Manager,dc=authldap,dc=edu,dc=cn";
// String password = "zut_gh_2018@";
String url = redisOpsUtil.getValue("ldap_url");
String base = redisOpsUtil.getValue("ldap_base");
String userDn = redisOpsUtil.getValue("ldap_user_dn");
String password = redisOpsUtil.getValue("ldap_password");
try {
LdapTemplate template = this.getLdapTemplate(url, base, userDn, password);
String filter = "(&(objectclass=person)(uid=" + uid + "))";
List<LdapUserInfo> foundLdapUserList = template.search("ou=People", filter, new AttributesMapper() {
@Override
public Object mapFromAttributes(Attributes attributes) throws NamingException {
LdapUserInfo ldapUserInfo = new LdapUserInfo();
Attribute a = attributes.get("cn");
if (a != null) {
ldapUserInfo.setCn((String) a.get());
}
a = attributes.get("uid");
if (a != null) {
ldapUserInfo.setUid((String) a.get());
}
a = attributes.get("userPassword");
if (a != null) {
ldapUserInfo.setUserPassword(new String((byte[]) a.get()));
}
return ldapUserInfo;
}
});
if (!CollectionUtils.isEmpty(foundLdapUserList)) {
return foundLdapUserList.get(0);
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 用于用户密码在LDAP进行验证
*
* @param ldappw LDAP中取出的用户密码
* @param inputpw 用户输入的用户密码
* @return 是否验证通过
* @throws NoSuchAlgorithmException
*/
private boolean verifySHA(String ldappw, String inputpw)
throws NoSuchAlgorithmException {
// MessageDigest 提供了消息摘要算法,如 MD5 或 SHA,的功能,这里LDAP使用的是SHA-1
MessageDigest md = MessageDigest.getInstance("SHA-1");
// 取出加密字符
if (ldappw.startsWith("{SSHA}")) {
ldappw = ldappw.substring(6);
} else if (ldappw.startsWith("{SHA}")) {
ldappw = ldappw.substring(5);
}
// 解码BASE64
byte[] ldappwbyte = Base64.decode(ldappw.getBytes());
byte[] shacode;
byte[] salt;
// 前20位是SHA-1加密段,20位后是最初加密时的随机明文
if (ldappwbyte.length <= 20) {
shacode = ldappwbyte;
salt = new byte[0];
} else {
shacode = new byte[20];
salt = new byte[ldappwbyte.length - 20];
System.arraycopy(ldappwbyte, 0, shacode, 0, 20);
System.arraycopy(ldappwbyte, 20, salt, 0, salt.length);
}
// 把用户输入的密码添加到摘要计算信息
md.update(inputpw.getBytes());
// 把随机明文添加到摘要计算信息
md.update(salt);
// 按SSHA把当前用户密码进行计算
byte[] inputpwbyte = md.digest();
// 返回校验结果
return MessageDigest.isEqual(shacode, inputpwbyte);
}
/**
* 校验ldap登陆密码
*
* @param userName
* @param password
* @return
* @throws NoSuchAlgorithmException
*/
private boolean checkLdapLogin(String userName, String password) throws NoSuchAlgorithmException {
LdapUserInfo ldapUserInfo = searchLdapUerInfo(userName);
if (ldapUserInfo != null) {
return this.verifySHA(ldapUserInfo.getUserPassword(), password);
}
return false;
}
三、python3 调用范例
from ldap3 import Server, Connection, ALL, SUBTREE
import pymysql
LDAP_SERVER_URL = '10.10.1.155:1389'
ADMIN_DN = "uid=adminread,ou=People,dc=zzptc,dc=com"
ADMIN_PASSWORD = "adminread"
SEARCH_BASE = "dc=zzptc,dc=com"
DB_URL = "10.10.1.88"
DB_USER_NAME = "root"
DB_USER_PWD = "JYKJ168@joywise.net"
def get_ldap_users():
print("======get_ldap_users=======")
db = pymysql.connect(DB_URL, DB_USER_NAME, DB_USER_PWD, "base-platform")
cursor = db.cursor()
sql = "select userName from t_user "
cursor.execute(sql)
user_name_list = cursor.fetchall()
cursor.close()
db.close()
server = Server(LDAP_SERVER_URL, get_info=ALL)
conn = Connection(server, ADMIN_DN, ADMIN_PASSWORD, auto_bind=True)
conn.open()
conn.bind()
user_list = []
for user_name in user_name_list:
filter_condition = '(&(objectclass=person)(uid=%s))' % user_name
filter_attributes = ['alias-list', 'uid', 'cn', 'userPassword']
res = conn.search(SEARCH_BASE, filter_condition, attributes=filter_attributes)
if res:
entry = conn.entries[0]
u = {'uid': entry['uid'], 'userPassword': str(entry['userPassword'])[2:-1],
'alias-list': entry['alias-list'], 'cn': entry['cn']}
print(u)
user_list.append(u)
return user_list