2016年5月19日 星期四

【JAVA】LDAPS整合AD驗證

參考來源:ActiveDirectoryAuthentication
原作者是使用ldap,下面修改為ldaps驗證
為了安全考量,有一份堅持,
原作者是用nsLookup去取得AD的主機列表,
下面是先hardcode加上一台AD主機位址,並且加上憑證
當然憑證要匯到JDK的ca路徑
範例網域:contoso.com,當然要換成你要用的Domain


package com.uitox.shared.util;

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;

import javax.naming.CommunicationException;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.security.auth.login.AccountException;
import javax.security.auth.login.FailedLoginException;
import javax.security.auth.login.LoginException;

public class ActiveDirectoryAuthentication {
 private static final String CONTEXT_FACTORY_CLASS = "com.sun.jndi.ldap.LdapCtxFactory";

 private String ldapServerUrls[]={"ldap://fdc.contoso.com:636"};

 private int lastLdapUrlIndex;

 private final String domainName;

 public ActiveDirectoryAuthentication(String domainName) {
  this.domainName = domainName.toUpperCase();

  try {
//   ldapServerUrls = nsLookup(domainName);  
  } catch (Exception e) {
   e.printStackTrace();
  }
  lastLdapUrlIndex = 0;
 }

 public boolean authenticate(String username, String password)
   throws LoginException {
  if (ldapServerUrls == null || ldapServerUrls.length == 0) {
   throw new AccountException("Unable to find ldap servers");
  }
  if (username == null || password == null
    || username.trim().length() == 0
    || password.trim().length() == 0) {
   throw new FailedLoginException("Username or password is empty");
  }
  int retryCount = 0;
  int currentLdapUrlIndex = lastLdapUrlIndex;
  do {
   retryCount++;
   try {
    Hashtable env = new Hashtable();
    env.put(Context.INITIAL_CONTEXT_FACTORY, CONTEXT_FACTORY_CLASS);
    env.put(Context.PROVIDER_URL,
      ldapServerUrls[currentLdapUrlIndex]);
    env.put(Context.SECURITY_PROTOCOL, "ssl");

    System.setProperty("javax.net.ssl.trustStore","/jre/lib/security/cacerts");
    System.setProperty("javax.net.ssl.trustStorePassword","changeit");

    env.put(Context.SECURITY_PRINCIPAL, username + "@" + domainName);
    env.put(Context.SECURITY_CREDENTIALS, password);
    DirContext ctx = new InitialDirContext(env);
    ctx.close();
    lastLdapUrlIndex = currentLdapUrlIndex;
    return true;
   } catch (CommunicationException exp) {
    // TODO you can replace with log4j or slf4j API
    exp.printStackTrace();
    // if the exception of type communication we can assume the AD
    // is not reachable hence retry can be attempted with next
    // available AD
    if (retryCount < ldapServerUrls.length) {
     currentLdapUrlIndex++;
     if (currentLdapUrlIndex == ldapServerUrls.length) {
      currentLdapUrlIndex = 0;
     }
     continue;
    }
    return false;
   } catch (Throwable throwable) {
    throwable.printStackTrace();
    return false;
   }
  } while (true);
 }

 private static String[] nsLookup(String argDomain) throws Exception {
  try {
   Hashtable env = new Hashtable();
   env.put(Context.INITIAL_CONTEXT_FACTORY,
     "com.sun.jndi.dns.DnsContextFactory");
   env.put("java.naming.provider.url", "dns:");
   DirContext ctx = new InitialDirContext(env);
   Attributes attributes = ctx.getAttributes(
     String.format("_ldap._tcp.%s", argDomain),
     new String[] { "srv" });
   // try thrice to get the KDC servers before throwing error
   for (int i = 0; i < 3; i++) {
    Attribute a = attributes.get("srv");
    if (a != null) {
     List domainServers = new ArrayList();
     NamingEnumeration enumeration = a.getAll();
     while (enumeration.hasMoreElements()) {
      String srvAttr = (String) enumeration.next();
      // the value are in space separated 0) priority 1)
      // weight 2) port 3) server
      String values[] = srvAttr.toString().split(" ");
      domainServers.add(String.format("ldap://%s:%s",
        values[3], values[2]));
     }
     String domainServersArray[] = new String[domainServers
       .size()];
     domainServers.toArray(domainServersArray);
     return domainServersArray;
    }
   }
   throw new Exception("Unable to find srv attribute for the domain "
     + argDomain);
  } catch (NamingException exp) {
   throw new Exception("Error while performing nslookup. Root Cause: "
     + exp.getMessage(), exp)
  }
 }
}
主要改造說明
  1. ldapServerUrls直接加入主機 22行處改成,加上DC主機,ex:ldap://fdc.contoso.com:636
    
     private String ldapServerUrls[]={"ldap://fdc.contoso.com:636"};
    
    註解掉原nsLookup查表,因為我hardcode了 XD
  2. 指定憑證CA檔 先將企業內部root CA匯入,至/jre/lib/security/cacerts
    然後指定CA檔,讓java知道就行了
    
        System.setProperty("javax.net.ssl.trustStore","<JDK ROOT>/jre/lib/security/cacerts");
        System.setProperty("javax.net.ssl.trustStorePassword","changeit");
    
最後,在寫一個驗證的Class,作登錄就大功告成。

public class UserCredentialManager {
        ...
 public synchronized void login(String name, String password){
  boolean IsAuth = false;
  ActiveDirectoryAuthentication ADAuth = new ActiveDirectoryAuthentication("CONTOSO.COM");
  try {
   IsAuth = ADAuth.authenticate(name, password);
  } catch (LoginException e) {
   e.printStackTrace();
  }
  
  if(IsAuth == true){
   user = name;
  }
  
 }
        ...
}

沒有留言:

張貼留言