1 2 3 Previous Next 40 Replies Latest reply: Feb 24, 2009 12:03 PM by 843793 RSS

    JNDI, Active Directory and Authentication (part 5)

    800477
      Many developers attempt to use LDAP Directories as an authentication service. While LDAP is a directory protocol primarily designed to search, add, delete and modify entries stored in the directory, implicit in the protocol is the ability to authenticate LDAP connections using a variety of authentication mechanisms.

      For example Active Directory supports simple (clear text), HTTP-DIGEST, X.509 Client Certificates and Kerberos (via GSS-API). For details of these mechanisms, refer to other postings in this forum; JNDI, Active Directory and Authentication (parts 1 - 4)

      When using a LDAP Directory as a simple authentication service, the typical approach is to gather a user's credentials (username & password) and verify these against the username and password values stored in the directory.

      For obvious security reasons Active Directory does not permit read operations against the Windows password attribute( unicodePassword), thereby preventing an attacker from retrieving the password and attempting to crack the password offline. Therefore the only way to verify a user's credentials, is to actually perform a LDAP bind.

      Ordinarily when Active Directory authenticates a user, it assembles all of the authorization data and builds a Windows security token containing all of the user's security identifiers (group membership, privileges etc.). While this is appropriate for authenticating user's into a Windows network, it may incur additional performance overhead and may not be appropriate for many Intranet or Extranet application scenarios, where all that is required is a simple verification of a user's name & password.

      In order to support this simple scenario, Windows Server 2003 introduced a LDAP Connection Control that does not incur the overhead of assembling all of the user's Windows authorization information during the LDAP bind operation. This control is described at
      http://msdn.microsoft.com/library/default.asp?url=/library/en-us/ldap/ldap/ldap_server_fast_bind_oid.asp

      To use the Active Directory LDAP Fast Bind Control in Java & JNDI, simply request the control during the connection request. In this example of a server side of the application, the LdapContext is initialized with the connection control and subsequent authentication checks are performed by invoking the Context.reconnect method.
      /**
       * ldapfastbind.java
       * 
       * Sample JNDI application to use Active Directory LDAP_SERVER_FAST_BIND connection control
       * 
       */
      
      import java.util.Hashtable;
      import javax.naming.*;
      import javax.naming.ldap.*;
      import javax.naming.directory.*;
      
      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);
                }
           }
      }
      The "client" side of this code, simply instantiates the ldapfastbind class and calls the authenticate method a few times with different sets of credentials.
      /**
       * 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();
      
           }
      }
      These samples are not meant to provide examples of good java programming practice, they merely illustrate how to take advantage of the features of Active Directory.
        • 1. Re: JNDI, Active Directory and Authentication (part 5)
          843793
          Thanks, great post. Hit the spot one time.
          • 2. Re: JNDI, Active Directory and Authentication (part 5)
            843793
            Hi everyone:

            I'm new in JNDI.
            How do I get the name of te user making a Ldap Fast Bind ???
            I'm making a service where the user authenticate, but I need to show him his name and some other properties in the browser.

            Could you help me please !!!
            Regards
            • 3. Re: JNDI, Active Directory and Authentication (part 5)
              800477
              ????

              In the sample "client" code the user has already submitted their name.

              You then just need to search the directory and return whatever attributes you wish.
              • 4. Re: JNDI, Active Directory and Authentication (part 5)
                843793
                Hello,
                May you please provide an example on how you would go about searching using the username of an authenticated user to reteive attributes from AD? Thanking you in adavnce.

                Message was edited by:
                Locito
                • 5. Re: JNDI, Active Directory and Authentication (part 5)
                  800477
                  I don't understand your question.

                  Do you mean
                  1. How do you perform a search using the credentials of a user, or
                  2. How do you retrieve a user's attributes.

                  For 1, just use the user's credentials when you perform the LDAP bind.
                  Refer to the follwoing posts.
                  JNDI, Active Directory and Authentication (Part 1) (Kerberos)
                  http://forum.java.sun.com/thread.jspa?threadID=579829&tstart=300

                  JNDI, Active Directory & Authentication (part 2) (SSL)
                  http://forum.java.sun.com/thread.jspa?threadID=581425&tstart=50

                  JNDI, Active Directory & Authentication (part 3) (Digest-MD5)
                  http://forum.java.sun.com/thread.jspa?threadID=581868&tstart=150

                  JNDI, Active Directory & Authentication (part 4) (SASL EXTERNAL)
                  http://forum.java.sun.com/thread.jspa?threadID=641047&tstart=0

                  For 2., the user would have submitted their username when authenticating to your application. Their name would have been submitted either as:
                  distinguishedName; cn=Albert Einstein,ou=Physicists,dc=Antipodes,dc=com
                  userprincipalName; alberte@antipodes.com
                  NT Domain Name; ANTIPODES\alberte

                  Just perform a search using the appropriate search filter, for example if they used a distinguished name something like
                  (&objectClass=user)(cn=Albert Eisntein))
                  or if they used a userPrinciplaName, something like
                  (&(objectClass=user)(userPrincipalName=albertEinstein@antipodes.com))
                  Note that if the user's authenticate using Kerberos and use an existing ticket, then you would have to obtain the user name from the login Context Subject. Similarly, if the user authenticates using EXTERNAL (X.509 certificate), you will need to extract the user's name from the certificate.
                  Good luck.
                  • 6. Re: JNDI, Active Directory and Authentication (part 5)
                    843793
                    adler_steven, thank you for all your posts! specifically the ones I've read and studied (AD and JNDI related)
                    • 7. Re: JNDI, Active Directory and Authentication (part 5)
                      843793
                      nice job adler_steven!

                      This control can help a lot for authentication applications under heavy traffic. AD has lots of good stuff but people just doesn't know where to get them.

                      It would be great if someone can benchmark the code to see how much faster it would be, by camparing with the case without control.

                      Thanks,
                      SunJ
                      • 8. Re: JNDI, Active Directory and Authentication (part 5)
                        843793
                        Very useful information. Thank you.

                        One question- You have mentioned that one could use ssl by using adding an additional line to the server code- (env.put(Context.SECURITY_PROTOCOL,"ssl")

                        Is this all that is required? Just add this line and things use SSL automatically or do we need to mess around with certificates etc?

                        Thank you for your time.

                        Ray.
                        • 9. Re: JNDI, Active Directory and Authentication (part 5)
                          800477
                          Have a read of the post titled "JNDI, Active Directory & Authentication (part 2) (SSL)" available at http://forum.java.sun.com/thread.jspa?threadID=581425&tstart=50

                          You will need to import the certificate authority's root public key into your Java certificate store so that the certificate trust chain can be stablished.
                          • 10. Re: JNDI, Active Directory and Authentication (part 5)
                            843793
                            THANK YOU SO MUCH!!

                            This is what I was searching for for what seemed like an etternitiy. A clear consise explanation and a working code sample....


                            Thank you again!
                            • 11. Re: JNDI, Active Directory and Authentication (part 5)
                              843793
                              I am still stuck up with the -MS-ADAM, Create User code. The user gets created but unable to login. I tried to set msDS-UserAccountDisabled attribute but unsuccessfully.

                              Can anybody help me plz, how to write java-code that creates a user and set the account to password never expires in MS-ADAM.

                              Regards,
                              Sree.
                              • 12. Re: JNDI, Active Directory and Authentication (part 5)
                                843793
                                MS-ADAM user creation
                                import javax.naming.*;
                                import javax.naming.ldap.StartTlsResponse;
                                import javax.naming.ldap.StartTlsRequest;
                                import javax.naming.ldap.LdapContext;
                                import javax.naming.ldap.InitialLdapContext;
                                import javax.naming.directory.*;
                                import java.util.Hashtable;
                                import java.util.Enumeration;
                                import java.io.IOException;
                                import java.io.UnsupportedEncodingException;
                                
                                /**
                                 * Sample JNDI client ADD application to demonstrate how to create a new entry
                                 */
                                public class LDAPCreateUser {
                                public static void main(String[] args)  throws UnsupportedEncodingException {
                                Hashtable env = new Hashtable();
                                env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory");
                                env.put(Context.PROVIDER_URL, "ldap://machine:port/dc=ldapdn,dc=com");
                                env.put(Context.SECURITY_AUTHENTICATION, "simple");
                                env.put(Context.SECURITY_PROTOCOL, "ssl");
                                env.put(Context.SECURITY_PRINCIPAL, "cn=admin,dc=ldapdn,dc=com");
                                env.put(Context.SECURITY_CREDENTIALS, "pwd");
                                try {
                                     // Create the initial context
                                     // DirContext ctx = new InitialDirContext(env);
                                     LdapContext ctx = new InitialLdapContext(env,null);
                                     // The distinguished name of the new entry
                                     String dn = "cn="+args[0]+",OU=People";
                                
                                     // Create attributes to be associated with the new entry
                                     Attributes attrs = new BasicAttributes(true);
                                
                                     // Objectclass -- required in MUST list
                                Attribute oc = new BasicAttribute("objectclass"); // required by 'top'
                                          oc.add("top");
                                          oc.add("person");
                                          oc.add("organizationalPerson");
                                          oc.add("user");
                                          attrs.put(oc);
                                     System.out.println(args[0]);
                                     // Other mandatory attributes -- required in MUST list
                                          attrs.put("uid", args[0]);
                                          attrs.put("sn", "sn");      // change as you like
                                          attrs.put("givenName","gn");//-do--
                                          attrs.put("cn", args[0]);  // required by 'person'
                                
                                     // Optional attributes -- but they must be defined in schema
                                
                                     attrs.put("mail","jysmithe@mydomain.com");
                                     attrs.put("msDS-UserAccountDisabled", "FALSE");
                                     attrs.put("msDS-UserDontExpirePassword", "TRUE");
                                     attrs.put("ou", "people");
                                          // Create the context
                                          Context result = ctx.createSubcontext(dn, attrs);
                                     System.out.println("Created account for: " + dn);
                                
                                //set password is a ldap modfy operation
                                //and we'll update the userAccountControl
                                //enabling the acount and force the user to update ther password
                                //the first time they login
                                     ModificationItem[] mods = new ModificationItem[1];
                                //Replace the "unicdodePwd" attribute with a new value
                                //Password must be both Unicode and a quoted string
                                String newQuotedPassword = "\""+args[0]+"\"";
                                byte[] newUnicodePassword = newQuotedPassword.getBytes("UTF-16LE");
                                
                                
                                mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("userPassword", newUnicodePassword));
                                          // Perform the update
                                          ctx.modifyAttributes(dn, mods);
                                System.out.println("Set password & updated userccountControl");
                                
                                          // Close the contexts when we're done
                                          result.close();
                                          ctx.close();
                                
                                     }catch(NamingException e){
                                               e.printStackTrace();
                                     }
                                     }
                                }
                                • 13. Re: JNDI, Active Directory and Authentication (part 5)
                                  843793
                                  Hi All,

                                  Is my asssumption correct that this solution only works for Windows 2003 server? Is there a similar solution in the Windows 2000 server?
                                  • 14. Re: JNDI, Active Directory and Authentication (part 5)
                                    800477
                                    To be quite honest, I can't remember when the LDAP Fastbind control was added.

                                    And I no longer have a Win2K DC to play with.

                                    Simplest way to check is to see if the LDAP Fastbind Control is supported on Win2K is to check if the Control's OID "1.2.840.113556.1.4.1781" is listed in the supportedControls attribute of the RootDSE.
                                    1 2 3 Previous Next