一、为什么需要统一认证
日常办公经常会有多套系统,如果各个系统各自维护一套用户认证,用户需要记住多个用户名密码。 系统各自管理用户认证的方式,不但会有重复建设的问题,用户体验也会差,经常会有用户忘记密码的情况。
二、LDAP统一认证是什么
LDAP是Light weight Directory Access Protocol(轻量级目录访问协议)的缩写,它是基于X.500标准的轻量组播目录访问协议。
目录是一个为查询、浏览和搜索而优化的数据库,它成树状结构组织数据。目录数据库和关系数据库不同,它有优异的读性能,但写性能很差,没有事务处理、回滚等复杂操作,不适合存储修改频繁的数据。适合存储人员组织、电话簿和地址簿等信息。
三、LDAP的基本模型
3.1 信息模型
LDAP中信息以树状方式组织,数据的基本单元是条目,每个条目由属性构成,属性中存储有属性值。
3.2 命名模型
LDAP中的命名模型,也即LDAP中条目的定位方式。
每个条目有自己的DN,DN是该条目在整个树中的唯一名称标识,如同文件系统中带路径的文件名。
3.3 功能模型
LDAP中支持四类操作: 查询类操作、更新类操作、认证类操作和其它操作;
3.4 安全模型
LDAP的安全模型主要通过身份认证、安全通道和访问控制来实现。
四、LDAP认证的过程
4.1 访问LDAP认证服务架构图
4.2 身份验证的步骤
LDAP利用登录名和密码进行验证,进行身份验证通常需要以下步骤:
1、通过用户登录获取用户名密码。
2、匿名或默认用户绑定LDAP服务器,绑定成功后执行下面步聚。
3、根据输入的登录名,执行一个搜索。请求参数形如:"(|(uid={login})(mail={login}))“,请求如果返回一个entry,可以通过该entry得到DN,后面步聚使用。如果返回多个或没有返回,说明用户输入用户名有误,验证失败。
4、如果上一步验证成功,得到用户信息所在entry的DN,使用这个DN和用户输入password重新绑定LDAP服务器。如果绑定成功,说明验证成功。绑定失败,返回密码错误的信息。
4.3 为什么需要两次绑定
为什么基于LDAP进行验证需要“两次”绑定? 为什么不能直接取出密码进行比较?
主要是出于安全考虑,LDAP服务器对于password属性一般是不可读的。
4.4 LDAP搜索参数表达式
& 与(列表中所有项必须为true)
| 或(列表中至少一个必须为true)
! 非(求反的项不能为true)
= 相等(根据属性的匹配规则)
~= 近似等于(根据属性的匹配规则)
>= 大于(根据属性的匹配规则)
<= 小于(根据属性的匹配规则)
=* 存在(条目中必须有这个属性,但值不做限制)
* 通配符(表示这个位置可以有一个或多个字符),当指定属性值时用到
\ 转义符(当遇到“*”,“(”,“)”时进行转义)
五、如何在系统中集成LDAP认证
LDAP认证服务是跨平台,同时支持TCP/IP协议。在系统中两次绑定LDAP服务器成功,代表登录成功,否则登录失败。
下面以Java语言为例演示两次绑定的过程:
首先添加依赖:
com.novell.ldap
jldap
4.3
两次绑定代码:
public string bind(String username, String password) {
LDAPConnection ldapConnection = new LDAPConnection();
ldapConnection.connect(Constants.LDAP_HOST, Constants.LDAP_PORT);
ldapConnection.bind(LDAPConnection.LDAP_V3, Constants.LDAP_BIND_DN, Constants.LDAP_BIND_PASSWORD.getBytes("UTF8"));
try {
String filter = String.format("(|(mail=%s)(uid=%s))", username, username);
LDAPSearchResults results = ldapConnection.search(Constants.LDAP_BIND_BASE, LDAPConnection.SCOPE_SUB, filter, null, false);
LDAPEntry nextEntry, nextUserEntry;
while (results.hasMore()) {
try {
nextEntry = results.next();
} catch (LDAPException e) {
if (e.getResultCode() == LDAPException.LDAP_TIMEOUT || e.getResultCode() == LDAPException.CONNECT_ERROR) {
break;
} else {
continue;
}
}
String dn = nextEntry.getDN();
ldapConnection.bind(LDAPConnection.LDAP_V3, dn, password.getBytes("UTF8"));
LDAPSearchResults userResults = ldapConnection.search(Constants.LDAP_BIND_BASE, LDAPConnection.SCOPE_SUB, String.format("(|(mail=%s)(uid=%s))", username, username), null, false);
while (userResults.hasMore()) {
try {
nextUserEntry = userResults.next();
} catch (LDAPException e) {
if (e.getResultCode() == LDAPException.LDAP_TIMEOUT || e.getResultCode() == LDAPException.CONNECT_ERROR) {
break;
} else {
continue;
}
}
if (nextUserEntry == null) {
continue;
}
String userDn = nextUserEntry.getDN();
if (!Strings.isNullOrEmpty(userDn) && userDn.equals(dn)) {
//登录成功
}
}
}
} catch (LDAPException e) {
logger.warn("get LDAPException:", e);
} catch (UnsupportedEncodingException e) {
logger.warn("get UnsupportedEncodingException:", e);
} finally {
ldapConnection.disconnect();
}
return null;
}