一份 pdf 文件的文章,来源于 www.diybl.com 这个网站,本想直接找到原始出处,但是发现网站关闭了,pdf 文件又不方便随时查阅,所以把文章内容复制到这里,供查阅参考。
JNDI, Active Directory and Authentication (part 5) —–Simple
笔者最近接触到一些通过JNDI/LDAP来访问Microsoft Active Directory的知识,虽然也可以通过JNI来调用C++或者其他Microsoft的编程工具编译完成的DLL来调用一些特定的功能,例如通过使用C++调用ADSI接口来进
行用户的验证。
但是这就造成了平台依赖,如果目录服务换成了Sun Directory Service或者openldap,那么系统就不能调用DLL来实现对应的功能了,所以应该使用JNDI/LDAP的方式,因为无论是什么目录服务,都实现了LDAP标准,所以针对接口进行编程即可。
笔者需要完成这样一个功能,通过JNDI/LDAP编程的方式来实现Microsoft Active Directory的用户验证。
本文描述了使用Simple方式来进行验证。
很多开发者使用LDAP目录作为一个验证服务。但是LDAP作为一个目录协议,主要被设计用来搜索,添加,删除以及修改存储在目录中的实体,协议中暗含了通过使用一系列验证方法来验证LDAP连接。这些验证方法包括Simple(明文)、HTTP-DIGEST、X.509客户端证书以及Kerberos(通过GSS-API)。
在本系列文章的第二、三、四篇中讲述了使用JAAS和GSSAPI并利用Kerberos作为验证协议来访问Active Directory、使用SSL通信方式来保证传输的安全性、描述了使用DIGEST-MD5方式来保证传输的数据完整性和机密性、以及
使用SASL EXTERNAL方式进行Active Directory验证。
当使用LDAP目录作为简单的验证服务时,典型的方法是获取用户的验证信息(用户名和密码),然后与存储在Active Directory中的用户名和密码相比较进行验证。
由于众所周知的安全原因,Active Directory不允许读取Windows密码(unicodePassword)属性,以此来防止攻击者获取密码或者以脱机方式破解密码。因此验证用户信息的唯一方式就是实现LDAP绑定。
通常情况下,当Active Directory验证一个用户的时候,它收集所有的验证数据和创建一个包含所有用户安全标志的Windows安全标记(组、成员、权限、…)。这适用于在Windows网络中验证用户,但是由可能导致
额外的性能需求,不一定适用于许多企业内部或者外部的情况,在这些情况下只需要简单的校验用户名和密码。
Windows Server 2003介绍了一个LDAP连接控制来支持这种简单的情况,减少信息的收集。详细信息参考:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/ldap/ldap/ldap_server_fast_bind_oid.asp
为了在Java&JNDI中使用Active Directory LDAP Fast Bind Control,只需要在连接请求时请求控制。在下面这个应用程序的服务器端代码中,LdapContext通过连接控制来进行初始化,后续的验证通过调用Context.reconnect方法。
package com.sun.auth.simple;
/**
* ldapfastbind.java
*
* Sample JNDI application to use Active Directory LDAP_SERVER_FAST_BIND connection control
*
*/
import java.util.Hashtable;
import javax.naming.AuthenticationException;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.ldap.Control;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.LdapContext;
class FastBindConnectionControl implements Control {
public byte[] getEncodedValue() {
return null;
}
public String getID() {
return "1.2.840.113556.1.4.1781";
}
public boolean isCritical() {
return true;
}
}
public class LdapFastBind {
public Hashtable env = null;
public LdapContext ctx = null;
public Control[] connCtls = null;
public LdapFastBind(String ldapurl) {
env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.SECURITY_AUTHENTICATION,"simple");
env.put(Context.PROVIDER_URL,ldapurl);
connCtls = new Control[] {new FastBindConnectionControl()};
//first time we initialize the context, no credentials are supplied
//therefore it is an anonymous bind.
try {
ctx = new InitialLdapContext(env,connCtls);
}
catch (NamingException e) {
System.out.println("Naming exception " + e);
}
}
public boolean Authenticate(String username, String password) {
try {
ctx.addToEnvironment(Context.SECURITY_PRINCIPAL,username);
ctx.addToEnvironment(Context.SECURITY_CREDENTIALS,password);
ctx.reconnect(connCtls);
System.out.println(username + " is authenticated");
return true;
}
catch (AuthenticationException e) {
System.out.println(username + " is not authenticated");
return false;
}
catch (NamingException e) {
System.out.println(username + " is not authenticated");
return false;
}
}
public void finito() {
try {
ctx.close();
System.out.println("Context is closed");
}
catch (NamingException e) {
System.out.println("Context close failure " + e);
}
}
}
客户端代码只是简单的初始化SimpleSearch类,然后通过设置不同的用户来调用authenticate方法。
package com.sun.auth.simple;
/**
* fastbindclient.java
*
* Sample JNDI application to use LDAP_SERVER_FAST_BIND connection control
*
* This is just a test harness to invoke the ldapfastbind methods
*/
class FastBindClient {
public static void main(String[] args) {
// Could also use ldaps over port 636 to protect the communication to
// the
// Active Directory domain controller. Would also need to add
// env.put(Context.SECURITY_PROTOCOL,"ssl") to the "server" code
String ldapurl = "ldap://mydc.antipodes.com:389";
boolean IsAuthenticated = false;
LdapFastBind ctx = new LdapFastBind(ldapurl);
IsAuthenticated = ctx.Authenticate("ANTIPODES\\alberte", "GoodPassword");
IsAuthenticated = ctx.Authenticate("ANTIPODES\\alberte", "BadPassword");
IsAuthenticated = ctx.Authenticate("ANTIPODES\\charlesd","GoodPassword");
IsAuthenticated = ctx.Authenticate("ANTIPODES\\charlesd", "BadPassword");
IsAuthenticated = ctx.Authenticate("ANTIPODES\\isaacn", "GoodPassword");
IsAuthenticated = ctx.Authenticate("ANTIPODES\\isaacn", "BadPassword");
ctx.finito();
}
}
至此,本系列文章告一段落,但是会继续发表一些关于LDAP和AD的文章。