跟蹤Sun JNDI LDAP Service Provider底層通訊
跟蹤Sun JNDI LDAP Service Provider底層通訊對Sun JNDI LDAP Service Provider底層與LDAP Server通訊進行跟蹤,獲取更多有用的debug信息。當然,很多情況下我們可以通過在伺服器端,或者負載均衡伺服器上使用tcpdump抓包,然後再使用類似ethereal的工具進行分析,可以獲得直觀清晰的數據。但是仍然有不足的地方:
1. 當用戶沒有伺服器的適當許可權,比如沒有root身份不能執行tcpdump
2. 當分析工具不支持解析中文時,而數據包中又存在中文字元時 所以,通過Sun JNDI LDAP Service Provider,直接獲取客戶端和Server間的通訊信息,同時可以對中文進行支持,以滿足特殊情況下的需要。
--
一般情況下,我們可以通過如下的設置來跟蹤Sun JNDI LDAP Service Provider與LDAP Server通訊的情況:
Hashtable p= new Hashtable();
p.put( DirContext.PROVIDER_URL, "ldap://localhost:389");
p.put( DirContext.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
p.put( DirContext.SECURITY_CREDENTIALS, "directory");
p.put( DirContext.SECURITY_PRINCIPAL, "cn=Directory Manager");
p.put( "com.sun.jndi.ldap.trace.ber", System.out);
InitialDircontext ctx= new InitialDirContext( p);
然後我們進行LDAP相關的操作:
public static void main( String[] args) throws Exception {
DirContext ctx= null;
try {
ctx= getCtx();
SearchControls cons= new SearchControls();
cons.setSearchScope( 1);
NamingEnumeration enu= ctx.search( "dc=com", "objectclass=*", cons);
enu.close();
}catch ( NamingException ex) {
ex.printStackTrace();
}finally {
try {
if ( ctx!= null) ctx.close();
}catch ( Exception ex) {}
}
}
下面是默認的Hex格式輸出:
-> localhost:389 0000: 30 29 02 01 01 60 24 02 01 03 04 14 63 6E 3D 44 0)...`$.....cn=D
0010: 69 72 65 63 74 6F 72 79 20 4D 61 6E 61 67 65 72 irectory Manager
0020: 80 09 64 69 72 65 63 74 6F 72 79 ..directory
<- localhost:389 0000: 30 0C 02 01 01 61 07 0A 01 00 04 00 04 00 0....a........
-> localhost:389 0000: 30 4F 02 01 02 63 2D 04 0D 64 63 3D 74 61 6E 6E 0O...c-..dc=tann
0010: 69 6E 2E 63 6F 6D 0A 01 01 0A 01 03 02 01 00 02 in.com..........
0020: 01 00 01 01 00 87 0B 6F 62 6A 65 63 74 63 6C 61 .......objectcla
0030: 73 73 30 00 A0 1B 30 19 04 17 32 2E 31 36 2E 38 ss0...0...2.16.8
0040: 34 30 2E 31 2E 31 31 33 37 33 30 2E 33 2E 34 2E 40.1.113730.3.4.
0050: 32 2
<- localhost:389 0000: 30 7F 02 01 02 64 7A 04 2A 63 6E 3D 44 69 72 65 0....dz.*cn=Dire
0010: 63 74 6F 72 79 20 41 64 6D 69 6E 69 73 74 72 61 ctory Administra
0020: 74 6F 72 73 2C 20 64 63 3D 74 61 6E 6E 69 6E 2E tors, dc=tannin.
0030: 63 6F 6D 30 4C 30 28 04 0B 6F 62 6A 65 63 74 43 com0L0(..objectC
0040: 6C 61 73 73 31 19 04 03 74 6F 70 04 12 67 72 6F lass1...top..gro
0050: 75 70 6F 66 75 6E 69 71 75 65 6E 61 6D 65 73 30 upofuniquenames0
0060: 20 04 02 63 6E 31 1A 04 18 44 69 72 65 63 74 6F ..cn1...Directo
0070: 72 79 20 41 64 6D 69 6E 69 73 74 72 61 74 6F 72 ry Administrator
0080: 73 s
-> localhost:389 0000: 30 23 02 01 03 50 01 02 A0 1B 30 19 04 17 32 2E 0#...P....0...2.
0010: 31 36 2E 38 34 30 2E 31 2E 31 31 33 37 33 30 2E 16.840.1.113730.
0020: 33 2E 34 2E 32 3.4.2
-> localhost:389 0000: 30 22 02 01 04 42 00 A0 1B 30 19 04 17 32 2E 31 0"...B...0...2.1
0010: 36 2E 38 34 30 2E 31 2E 31 31 33 37 33 30 2E 33 6.840.1.113730.3
0020: 2E 34 2E 32 .4.2
<- localhost:389 0000: 30 5B 02 01 02 64 56 04 18 6F 75 3D 47 72 6F 75 0[...dV..ou=Grou
0010: 70 73 2C 20 64 63 3D 74 61 6E 6E 69 6E 2E 63 6F ps, dc=tannin.co
0020: 6D 30 3A 30 28 04 0B 6F 62 6A 65 63 74 43 6C 61 m0:0(..objectCla
0030: 73 73 31 19 04 03 74 6F 70 04 12 6F 72 67 61 6E ss1...top..organ
0040: 69 7A 61 74 69 6F 6E 61 6C 75 6E 69 74 30 0E 04 izationalunit0..
0050: 02 6F 75 31 08 04 06 47 72 6F 75 70 73 .ou1...Groups
<- localhost:389 0000: 30 5B 02 01 02 64 56 04 18 6F 75 3D 50 65 6F 70 0[...dV..ou=Peop
0010: 6C 65 2C 20 64 63 3D 74 61 6E 6E 69 6E 2E 63 6F le, dc=tannin.co
0020: 6D 30 3A 30 28 04 0B 6F 62 6A 65 63 74 43 6C 61 m0:0(..objectCla
0030: 73 73 31 19 04 03 74 6F 70 04 12 6F 72 67 61 6E ss1...top..organ
0040: 69 7A 61 74 69 6F 6E 61 6C 75 6E 69 74 30 0E 04 izationalunit0..
0050: 02 6F 75 31 08 04 06 50 65 6F 70 6C 65 .ou1...People
<- localhost:389 0000: 30 81 9B 02 01 02 64 81 95 04 1E 6F 75 3D 53 70 0.....d....ou=Sp
0010: 65 63 69 61 6C 20 55 73 65 72 73 2C 64 63 3D 74 ecial Users,dc=t
0020: 61 6E 6E 69 6E 2E 63 6F 6D 30 73 30 28 04 0B 6F annin.com0s0(..o
0030: 62 6A 65 63 74 43 6C 61 73 73 31 19 04 03 74 6F bjectClass1...to
0040: 70 04 12 6F 72 67 61 6E 69 7A 61 74 69 6F 6E 61 p..organizationa
0050: 6C 55 6E 69 74 30 15 04 02 6F 75 31 0F 04 0D 53 lUnit0...ou1...S
0060: 70 65 63 69 61 6C 20 55 73 65 72 73 30 30 04 0B pecial Users00..
0070: 64 65 73 63 72 69 70 74 69 6F 6E 31 21 04 1F 53 description1!..S
0080: 70 65 63 69 61 6C 20 41 64 6D 69 6E 69 73 74 72 pecial Administr
0090: 61 74 69 76 65 20 41 63 63 6F 75 6E 74 73 ative Accounts
<- localhost:389 0000: 30 0C 02 01 02 65 07 0A 01 00 04 00 04 00 0....e........
我們雖然取到了底層通訊的數據,但是根本無法直接閱讀,因此有兩種方法可以輸出符合我們習慣的內容:
1. 通過反編譯Sun的class文件,來獲取LDAPMessage格式的輸出
2. 增加一個FilterOutputStream,對Sun輸出的內容進行處理,處理后輸出LDAPMessage格式的內容
兩種方法都可以實現,但是第二種性能很差,Sun既要花時間格式化成Hex格式,我們還要在FilterOutputStream中反其道而行之,重新恢復成原始數據,然後再解析,過程中涉及到大量的String - byte[] 操作,因此性能比較差。但是不像第一種,第一種應該是一種違法的行為。
按照我們的要求格式化后的輸出如下:
-> localhost:389 LDAPMessage {
messageID = 1,
protocolOp = {
bindRequest = {
version = 3,
name = cn=Directory Manager,
authentication = {
simple = directory }
}
} }
<- localhost:389 LDAPMessage {
messageID = 1,
protocolOp = {
bindResponse = {
resultCode = 0,
matchedDN = ,
errorMessage =
}
} }
-> localhost:389 LDAPMessage {
messageID = 2,
protocolOp = {
searchRequest = {
baseObject = dc=tannin.com,
scope = 1,
derefAliases = 3,
sizeLimit = 0,
timeLimit = 0,
typesOnly = false,
filter = {
present = objectclass },
attributes = {
}
}
}
,
controls = {
{
controlType = 2.16.840.1.113730.3.4.2,
criticality = false
}
}
}
<- localhost:389 LDAPMessage {
messageID = 2,
protocolOp = {
searchResEntry = {
objectName = cn=Directory Administrators, dc=tannin.com,
attributes = {
{
type = objectClass,
vals = {
top,
groupofuniquenames
}
},
{
type = cn,
vals = {
Directory Administrators
}
}
}
} } }
-> localhost:389 LDAPMessage {
messageID = 3,
protocolOp = {
abandonRequest = 2 }
,
controls = {
{
controlType = 2.16.840.1.113730.3.4.2,
criticality = false
}
}
}
<- localhost:389 LDAPMessage {
messageID = 2,
protocolOp = {
searchResEntry = {
objectName = ou=Groups, dc=tannin.com,
attributes = {
{
type = objectClass,
vals = {
organizationalunit,
top
}
},
{
type = ou,
vals = {
Groups
}
}
}
} } }
-> localhost:389 LDAPMessage {
messageID = 4,
protocolOp = {
unbindRequest = NULL }
,
controls = {
{
controlType = 2.16.840.1.113730.3.4.2,
criticality = false
}
}
}
[*]由於LDAP協議規定,LDAP Message是符合ASN.1 BER規則的位元組流,但是我們沒有必要自己來實現Ber Encoder/Decoder。我使用了很早以前IBM的Snacc for Java,它可以自動根據LDAP V3 ASN Definitions來生成Java類文件,然後使用它的Encoder/Decoder就可以處理了。由於IBM已經移除了Snacc,並且已經不再是免費的軟體,所以使用上受到限制。OpenSource正有幾個Project關於ASN Compiler的,但是還不夠成熟,相信不久的將來就可以取代Snacc。[*]從上面的Trace中也可以看出,Sun在處理NamingEnumeration.close()處有一個缺陷,即當對一個NamingEnumeration實例在沒有完成全部遍歷時,執行close()操作會導致Sun LDAP SP 向LDAP Server發送一個LDAP Abandon Request。因此我們需要在即使只有一個返回值的情況下也要完成遍歷,或者乾脆不關閉NamingEnumeration來避免這種情況。
《解決方案》
介紹:
跟蹤JNDI底層與LDAP的交互,並以LDAP DEFINITIONS(ANS.1)格式輸出,同時可以支持中文顯示。
使用方法:
1. 將ldapmessage_trace.jar包加入到boot classpath中,即使用參數-Xbootclasspath/p:,比如:
java -Xbootclasspath/p:ldapmessage_trace.jar <your main class>
同時在代碼中需要進行如下的設置,示例如下:
/**
*
* @return
* @throws NamingException
*/
public static DirContext getCtx() throws NamingException {
Hashtable p= new Hashtable();
p.put( DirContext.PROVIDER_URL, "ldap://localhost:389");
p.put( DirContext.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
p.put( DirContext.SECURITY_CREDENTIALS, "directory");
p.put( DirContext.SECURITY_PRINCIPAL, "cn=Directory Manager");
p.put( "com.sun.jndi.ldap.trace.ber", System.out);
return new InitialDirContext( p);
}
如果需要輸出到文件中,可以自己起一個File Stream,然後設置到Context中。只要該對象是OutputStream子類就可以。
一切準備就緒,執行測試程序就可以從我們指定的輸出流中輸出LDAPMessage內容了。
2. 使用LdapMessageOutputStream來作為輸出流,但是這種方法會導致較低的效率
使用方法非常簡單,在獲取DirContext的時候指定LdapMessageOutputStream作為輸出流就可以了。
/**
*
* @return
* @throws NamingException
*/
public static DirContext getCtx() throws NamingException {
Hashtable p= new Hashtable();
p.put( DirContext.PROVIDER_URL, "ldap://localhost:389");
p.put( DirContext.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
p.put( DirContext.SECURITY_CREDENTIALS, "directory");
p.put( DirContext.SECURITY_PRINCIPAL, "cn=Directory Manager");
p.put( "com.sun.jndi.ldap.trace.ber", System.out);
return new InitialDirContext( p);
}
資源:
需要IBM Snacc4j的支持。Snacc已經被IBM廢除掉了,相關的使用也不再免費。希望日後開源社區的幾個ASN Compiler項目最終取代古老的Snacc……