Baidu脑残,把原来的空间改得不伦不类。所以把一些技术的东西挪到这里。
我找到两种方法,大同小异,第一种是通过Spring,适合已经采用Spring的项目。
一般来说用户名和密码都是保存在数据库中。现在有这个需求,用户名和密码是保存在M$的AD中。那么如何进行认证。我找到的方法有两种,一种是jcifs,另一种是Spring Security。这里用了Spring Security,参考了CAS SSO中LDAP验证的实现。
首先建一个测试环境。在Server 2003中安装Domain,这个没啥说的,网上文章一大把。安装好以后,创建测试用的用户组(Team1)和两个用户(test,test1)。test用户属于组Team1,test1则不属于。如下图。Domain的名称是dc.testdc.com。
创建好以后,在Eclipse中创建Java项目,加入以下的需要的Jar包:
开始写代码。初始化部分,一些LDAP的参数设置。
static {
LdapContextSource cs = new LdapContextSource();
cs.setCacheEnvironmentProperties(false);
cs.setUrl("ldap://192.168.1.200:389");
cs.setBase("CN=Users,DC=dc,DC=testdc,DC=com");
cs.setAuthenticationSource(new AuthenticationSource() {
public String getCredentials() {
return "";
}
public String getPrincipal() {
return "CN=Administrator,CN=Users,DC=dc,DC=testdc,DC=com";
}
});
template = new LdapTemplate(cs);
}
说明。
1. 其中 ldap://192.168.1.200:389 是上面的Server 2003的地址。389是AD默认的端口,一般不用改;
2. setBase("CN=Users,DC=dc,DC=testdc,DC=com") 是查找的基础。按我自己的理解,这相当于一个查询,会查出若干结果。实际进行认证的用户必须在这些结果中。这个字符串怎么得到的呢,前面的CN=Users表明是进行用户查询;后面的DC=dc,DC=testdc,DC=com是和上面图中左上角红圈中的dc.testdc.com匹配。后面会更详细说明怎么通过Softerra LDAP Browser来帮忙取得这些值。
3. getPrincipal() 返回的是一个用户,这个用户有权限进行(2)中的查询。这里为了省事用的是Administrator,当然肯定有这个权限了。
4. getCredentials() 这里自然就是(3)中用户的密码了。
进行下一步前用Softerra LDAP Browser来确认一下上面输入的东西。
去下载安装这个东西(Free的就够用),安装有,右键,选择New:
下一步,随便起个名
下一步,输入AD服务器的IP,端口,点击Fetch Base DN:
稍等,会出来一个列表,显示在Base DN下拉框中:
这里选择的是DC=dc,DC=testDC,DC=com,下一步
这里的用户名是CN=Administrator,CN=Users, DC=dc,DC=testdc,DC=com和Java代码中一致。下面的密码就填他的密码。下一步。如果有问题,会弹出错误。应该是没有错误的。
最后一步,默认的选择即可,点完成。
完成后,在界面中显示了各种LDAP的对象。这里有用的是Users下面的,因为我们要对它进行认证:
可以看到,Administrator和刚才创建的用户test,test1都在这里:
然后继续写代码。
下面这段代码的含义,是调用LDAP的查询,查询给出的用户名/密码是否满足条件。这里说的条件,是一个Filter字符串:
final String filter = "sAMAccountName=" + username;
template.search(
new SearchExecutor() {
public NamingEnumeration executeSearch(final DirContext context) throws NamingException {
return context.search(base, filter, searchControls);
}
},
new NameClassPairCallbackHandler(){
public void handleNameClassPair(final NameClassPair nameClassPair) {
cns.add(nameClassPair.getNameInNamespace());
}
});
换句话说,上面的查询,可以这样理解。首先,我们通过Search Base的设置(setBase("CN=Users,DC=dc,DC=testdc,DC=com"))设置了查询的范围。然后上面的filter = "sAMAccountName=" + username; 设置了查询的条件。例如sAMAccountName=test的意思就是在"CN=Users,DC=dc,DC=testdc,DC=com"所表示的这么多对象中,查找sAMAccountName=test的对象。换句话说,就是查找登录的用户,在AD服务器中是否存在? 然后程序中继续判断:
if (cns.isEmpty()) {
System.out.println("Search for " + filter + " returned 0 results.");
return false;
}
if (cns.size() > 1) {
System.out.println("Search for " + filter + " returned multiple results, which is not allowed.");
return false;
}
不存在,或者多余一个存在,都是错误。返回;
for (final String dn : cns) {
DirContext test = null;
String finalDn = dn;
try {
System.out.println("Performing LDAP bind with credential: " + dn);
test = template.getContextSource().getContext(
finalDn,
password);
if (test != null) {
return true;
}
} catch (final Exception e) {
e.printStackTrace();
} finally {
if (test != null) {
test.close();
}
}
}
到这里基本部分的验证结束了。功能上,可以验证整个AD中存在并且合法的用户。
那么,用户组的需求怎么实现?比如只有用户组Team1中的用户可以登录?
答案是上面的Filter。上面我们用了一个最简单的Filter,filter = "sAMAccountName=" + username。自然而然,可以就可以想到更改这个Filter,让它返回"属于用户组Team1并且用户名是xxxx"的用户。写法很简单,只需要改成
final String filter = "(&(CN=" + username + ")(memberof=CN=Team1,CN=Users,DC=dc,DC=testdc,DC=com))";
即可。上面的filter,有两个条件:
1. CN=用户名
2. memberof=CN=Team1,CN=Users,DC=dc,DC=testdc,DC=com,也就是用户是(CN=Team1,CN=Users,DC=dc,DC=testdc,DC=com)的一个成员。
最前面的&表示这两个条件是相与的关系。
memberof的值是怎么来的呢,可以在LDAP Browser中查看一个Team1组中的用户,其memberof属性:
在测试Filter的时候,可以在LDAP Browser中进行测试(右键New Pfofile -- 属性-- Entry页,点击Filter右侧的漏斗图标,可以在FilterBuilder
中测试)
最后有个关于用户被禁用的问题。如果用户被禁用了,上面的代码会抛出一个异常,需要捕捉异常。如果不想捕捉异常,可以使用
(!(userAccountControl:1.2.840.113556.1.4.803:=2))
来过滤出所有没有被禁用的用户。
上面完整的代码放在http://一一五.com/file/be6o06r5#TestSpringLDAP.java上,需要的可以参考,建议使用前重构下,结构比较乱