I want to search a user from LDAP and after getting the user I want to connect (validate)
that particular user using his DN and Password
I have successfully getting the DN but dont know how to bind it?
Here is an example that I took from the official documentation :
// Set up the environment for creating the initial context
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=JNDITutorial");
// Authenticate as S. User and password "mysecret"
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, "cn=S. User, ou=NewHires, o=JNDITutorial");
env.put(Context.SECURITY_CREDENTIALS, "mysecret");
DirContext ctx = new InitialDirContext(env);
You have to choose your right authentication model. I have tried it before and worked fine.
The LDAP bind() operation corresponds to the following in JNDI:
Constructing an InitialDirContext or InitialLdapContext with enough information in the environment to cause a login, i.e. a security principal and credentials, or
Calling reconnect() on an LdapContext initially obtained without any security information in the environment, or with security information relating to a different principal, but whose environment has subsequently been modified.
When a connection is made to a directory server using LDAP, the connection state is unauthenticated. Requests can be transmitted on an unauthenticated connection, assuming the server administrators permit unauthenticated requests. The BIND request is used to change authentication state of a connection.
Here is an example of searching and authenticating using the UnboundID LDAP SDK: SimpleBindExample.java. This example searches for an entry given a base object, naming attribute, and username, and then attempts to authenticate using a simple bind. Examples using a SASL bind could be constructed just as easily.
If you already have LdapContext opened using your credentials, you can copy it, change principal+credential in its environment and try reconnect:
LdapContext userContext = ldapContext.newInstance(null); // copy context
userContext.addToEnvironment(InitialDirContext.SECURITY_PRINCIPAL, userDn);
userContext.addToEnvironment(InitialDirContext.SECURITY_CREDENTIALS, password);
userContext.reconnect(null); // throws NamingException if creds wrong
userContext.close();
If it throws NamingException, credentials are wrong. It it is successful, credentials are ok. ;)
(This is useful if you have only LdapContext, but not the InitialDirContext, available.)
Related
I connect to an LDAP server as Bob:
Hashtable props = new Hashtable();
props.put(Context.SECURITY_PRINCIPAL, "cn=Bob,cn=Users,dc=myCompany,dc=com");
props.put(Context.SECURITY_CREDENTIALS, "Password1");
props.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
props.put(Context.PROVIDER_URL, url);
InitialLdapContext context = new InitialLdapContext(props, null);
Now I want to see Bob's name and email address.
I was expecting to find a method on context to return the attributes of the currently connected user, but I can't find one.
Bob doesn't have permission to search the directory, so I can't use context.search after connecting.
Is there a way to get these attributes without making another call to the LDAP server, or having to connect as someone else first?
You need to use the RFC 4532 'whoami' extended operation, OID=1.3.6.1.4.1.4203.1.11.3. You'll need to write yourself an ExtendedRequest and ExtendedResponse class for that (sorry can't post mine, but it's simple), and use it as follows:
WhoAmIExtendedResponse response = (WhoAmIExtendedResponse)context.extendedOperation(new WhoAmIExtendedRequest());
and then get the authZId out of the response. That's the DN of the current user, prefixed by "dn:". You then get his attributes via context.getAttributes("...")
I have a server process running on a machine KERBOS.COM and its trying to connect to a LDAP Server in IN.KERBOS.COM(Sub Realm) to sync user using GSSAPI Mechanism.
By looking at GSSLOGS we can see that
the spn its trying to authenticate belong to KERBOS.COM ldap/invr28ppqa36.in.kerbos.com#KERBOS.COM
Is this any way to make it pick IN.KERBOS.COM as realm.
The default realm has to be KERBOS.COM in krb5.conf . So changing the default realm to IN.KERBOS.COM is not an option.
I am also giving it fully qualified name so there should be some way to tell it to use IN.KERBOS.COM as realm.
env.put(Context.PROVIDER_URL, String.format("ldap://%s:%d", host, port));
Subject subject = new Subject();
subject.getPrivateCredentials().add(credential);
InitialLdapContext object = Subject.doAs(subject, new PrivilegedExceptionAction<InitialLdapContext>() {
public InitialLdapContext run() throws Exception {
env.put(Context.SECURITY_AUTHENTICATION, "GSSAPI");
return new InitialLdapContext(env, null);
} });
LOGS
Subject.doAs fails by throwing an exception (Intercept from Logs are given)
Found ticket for **sysadmin#IN.KERBOS.COM to go to krbtgt/IN.KERBOS.COM#IN.KERBOS.COM** Credentials acquireServiceCreds: obtaining service creds for **ldap/invr28ppqa36.in.kerbos.com#KERBOS.COM**
Using builtin default etypes for default_tgs_enctypes
default etypes for default_tgs_enctypes: 17 16 23 1 3.
KrbException: Fail to create credential. (63) - No service creds
at sun.security.krb5.internal.CredentialsUtil.acquireServiceCreds(CredentialsUtil.java:301)
at sun.security.krb5.Credentials.acquireServiceCreds(Credentials.java:442)
at sun.security.jgss.krb5.Krb5Context.initSecContext(Krb5Context.java:641)
at sun.security.jgss.GSSContextImpl.initSecContext(GSSContextImpl.java:248)
at sun.security.jgss.GSSContextImpl.initSecContext(GSSContextImpl.java:179)
at com.sun.security.sasl.gsskerb.GssKrb5Client.evaluateChallenge(GssKrb5Client.java:193)
at com.sun.jndi.ldap.sasl.LdapSasl.saslBind(LdapSasl.java:123)
at com.sun.jndi.ldap.LdapClient.authenticate(LdapClient.java:232)
at com.sun.jndi.ldap.LdapCtx.connect(LdapCtx.java:2740)
at com.sun.jndi.ldap.LdapCtx.<init>(LdapCtx.java:316)
at com.sun.jndi.ldap.LdapCtxFactory.getUsingURL(LdapCtxFactory.java:193)
at com.sun.jndi.ldap.LdapCtxFactory.getUsingURLs(LdapCtxFactory.java:211)
at com.sun.jndi.ldap.LdapCtxFactory.getLdapCtxInstance(LdapCtxFactory.java:154)
at com.sun.jndi.ldap.LdapCtxFactory.getInitialContext(LdapCtxFactory.java:84)
at javax.naming.spi.NamingManager.getInitialContext(NamingManager.java:684)
at javax.naming.InitialContext.getDefaultInitCtx(InitialContext.java:307)
at javax.naming.InitialContext.init(InitialContext.java:242)
at javax.naming.ldap.InitialLdapContext.<init>(InitialLdapContext.java:153)
at (LDAPConnector.java:101)
By default a Kerberos implementation will start at the KDC of the client realm, hoping that KDC can provide a cross-realm referral to the service realm. So, you have a ticket in EXAMPLE.COM, and you're going to a host called server.subdomain.example.com. Your client is supposed to go to the EXAMPLE.COM KDC. That KDC can either say that it knows how to give you a service ticket, or can give you a special referral ticket to get closer to the service; in this example you'd kind of expect it to give you a krbtgt/SUBDOMAIN.EXAMPLE.COM#EXAMPLE.COM realm. That tells your client to go to the subdomain.example.com KDC.
Apparently, your KERBOS.COM KDC doesn't know that your LDAP server is served by the IN.KERBOS.COM realm. One way you could solve things is by fixing your KDC. That's going to be dependent on what KDC software you have. Alternatively, you can edit your krb5.conf and tell your client that everything under in.kerbos.com is served by the IN.KERBOS.COM KDC. Add a section like the following to your krb5.conf:
[domain_realm]
.in.kerbos.com = IN.KERBOS.COM
See this as an example of discussing krb5.conf including domain_realm sections in Java. See RFC 6806 for technical details of how the referrals should work.
I am trying to authenticate a user through LDAP against Active Directory. Following is the code snippet I use:
private DirContext bindAsUser(String bindPrincipal, String password) {
Hashtable<String,String> env = new Hashtable<String,String>();
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, bindPrincipal);
env.put(Context.PROVIDER_URL, bindUrl);
env.put(Context.SECURITY_CREDENTIALS, password);
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.REFERRAL, "follow");
try {
return new InitialLdapContext(env, null);
} catch (NamingException e) {
e.printStackTrace()
}
}
The code for binding works if I provide:
Down-Level Logon Name, i.e. NetBIOSDomainName\sAMAccountName (e.g. domain\username), or
userPrincipalName (e.g. username#abc.com), or
distinguishedName (e.g. CN=username,OU=xxx,DC=abc,DC=com), or
objectSid (e.g. S-1-5-21-3623811015-3361044348-30300820-1013)
as the SECURITY_PRINCIPAL, while it failed if sAMAccountName (e.g. username) was used (I guess only the names which are unique within the forest are valid).
So what are the accepted patterns for SECURITY_PRINCIPAL? I searched a few similar questions, but none provide reference to official AD/LDAP documents. Or is it a configuration which I could lookup somewhere? Thanks!
From [MS-ADTS: Active Directory Technical Specification], the official doc for AD I guess.
http://msdn.microsoft.com/en-us/library/cc223499.aspx
Section "5.1.1.1.1 Simple Authentication" lists all the name forms supported by simple authentication.
I think you need check LDAP Principal Template. It specifies the principal authentication template required by your LDAP server. The principal authentication template is the format in which the authentication information for the security principal (the person who is logging in) must be passed to the LDAP server. The default value is ${email}, which is the format required by Microsoft Active Directory. Other LDAP servers require different authentication templates. Check with your network administrator to learn more about your LDAP server.
I'm using JNDI to change LDAP user's password. In most cases (when user's password isn't expired) this code works just fine:
public InitialLdapContext connect(String url, String securityPrincipal, String password) throws AuthenticationException, NamingException {
System.setProperty("javax.net.ssl.trustStore", truststore);
Properties env = new Properties();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, url);
env.put(Context.SECURITY_PRINCIPAL, "EE\\" + securityPrincipal);
env.put(Context.SECURITY_CREDENTIALS, password);
env.put(Context.SECURITY_PROTOCOL, "ssl");
env.put("java.naming.ldap.version", "3");
env.put(Context.REFERRAL, "follow");
return new InitialLdapContext(env,null);
}
But when user with expired password tries to change it my app throws:
Exception: javax.naming.AuthenticationException: [LDAP: error code 49 - 80090308: LdapErr: DSID-0C0903A9, comment: AcceptSecurityContext error, data 773, v1db1 ]
com.sun.jndi.ldap.LdapCtx.mapErrorCode(LdapCtx.java:3041)
com.sun.jndi.ldap.LdapCtx.processReturnCode(LdapCtx.java:2987)
com.sun.jndi.ldap.LdapCtx.processReturnCode(LdapCtx.java:2789)
com.sun.jndi.ldap.LdapCtx.connect(LdapCtx.java:2703)
com.sun.jndi.ldap.LdapCtx.<init>(LdapCtx.java:293)
com.sun.jndi.ldap.LdapCtxFactory.getUsingURL(LdapCtxFactory.java:175)
com.sun.jndi.ldap.LdapCtxFactory.getUsingURLs(LdapCtxFactory.java:193)
com.sun.jndi.ldap.LdapCtxFactory.getLdapCtxInstance(LdapCtxFactory.java:136)
com.sun.jndi.ldap.LdapCtxFactory.getInitialContext(LdapCtxFactory.java:66)
So my question is: Is it possible to change LDAP expired passwords? If it's possible, then tell how.
Thanx for help!
If you're using the password policy overlay you have to use the change-password extended request. It's not supported in the JDK but I've posted code for it in the Oracle Java JNDI forum.
The problem was resolved by creating Super User in Ad, which has rights to change every AD password. And when AD user password is expired, then the Super User changes his password.
I'm attempting to verify a user's password in some Java code:
private void bindToLdap(String un, String pw) throws NamingException, AuthenticationException {
log.debug ("Doing LDAP bind as user");
Hashtable<String, Object> env = new Hashtable<String, Object>();
env.put(Context.PROVIDER_URL, LdapConns.openLdapUrl);
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, "uid="+un+",ou=people,<base OU>");
env.put(Context.SECURITY_CREDENTIALS, pw);
DirContext dctx = new InitialDirContext(env);
log.debug("Context below:");
log.debug(dctx.getEnvironment().toString());
dctx.lookup("ou=people");
log.debug("connection context:");
log.debug(dctx.getEnvironment().toString());
log.debug ("Bound as user?");
}
Everything I've read so far indicates that I should take an exception when instantiating the DirContext if I've entered a bad password. This is not the case - I get a valid DirContext even if I enter a known bad password. I expect to catch an AuthenticationException at the dctx.lookup, I actually catch a NamingException.
In the debug log, I show:
[usermgmt.action.ChangeInternalPassword:bindToLdap:242] Context below:
[usermgmt.action.ChangeInternalPassword:bindToLdap:243] {java.naming.provider.url=ldaps://<ldap server>:636/, java.naming.factory.initial=org.apache.naming.java.javaURLContextFactory, java.naming.security.authentication=simple, java.naming.security.principal=nononyen_test, java.naming.factory.url.pkgs=org.apache.naming, java.naming.security.credentials=fubar!}
How do I verify that the password is actually correct? Do I need to perform a search on the LDAP db after binding to take the exception?
The LDAP database is OpenLDAP.
To determine whether an authentication ID and password is correct, applications must establish a connection to the directory server and then transmit a bind request and examine the response. The bind response from the server will contain an integer result code indicating the success or failure of the bind request, and may also contain response controls with some extra information about the status of the user entry.