I have to authenticate users into my system using LDAP, my problem is that i have many organizations and i dont have a list user-organization.
I have this code working forcing a knowing organization:
ldap.validate("uid=" + this.email + ",ou="+ThisIsTheOrImForcing+",ou=users,dc=myDC,dc=com,dc=br", this.password);
My validation method:
public boolean validate(String principal, String password){
// 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://IP:PORT/DC=opus");
//env.put(Context.PROVIDER_URL, "ldap://IP:PORT/dc=mydc,dc=com,dc=br");
env.put(Context.PROVIDER_URL, "ldap://IP:PORT/dc=mydc,dc=com,dc=br");
//Authenticate as S. User and password
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL,principal );
env.put(Context.SECURITY_CREDENTIALS, password);
try{
// Create the initial context
DirContext ctx = new InitialDirContext(env);
return true;
} catch( AuthenticationException ae ) {
return false;
} catch( NamingException ne ) {
return false;
}
}
I have the var ThisIsTheOrImForcing, how can i authenticate the user without knowing his organization, i have to search for it first, how?
Simple bind in LDAP requires you to provide the DN of the user, hence why you need to know the organization.
You have two ways to deal with it :
Make a search on your directory for the user which has the attribute uid = this.email , retrieve his DN, and authenticate with the found DN to validate the user
Use other way to authenticate, such as SASL mechanism : See this for more information. You will also have to look for your Directory configuration which mechanism is supported
Usually, uid is a unique identifier. A search operation is used to map the given uid to its distinguished name, so you don't have to worry about the exact place of an account in the directory.
Related
I am doing kerberos delegation. I noticed that GSSUtil.createSubject(context.getSrcName(), clientCred) returns a Subject without having credentials in it.
Prior to that i've done GSSCredential clientCred = context.getDelegCred(); which returns the credentials.
Edit: When I hit my service from one machine in same domain, it works, while if accessed from other machine in same domain, it doesn't.
Confused what additional settings are needed on AD ?
Any help is highly appreciated.
Following is my code:
public class KerberosTest {
public Subject loginImpl(byte[] kerberosTicket, String propertiesFileName) throws Exception {
System.setProperty("sun.security.krb5.debug", "true");
// // no effect // System.setProperty("javax.security.auth.useSubjectCredsOnly","false");
final Krb5LoginModule krb5LoginModule = new Krb5LoginModule();
Subject serviceUserSubject = new Subject();
final Map<String,String> optionMap = new HashMap<String,String>();
HashMap<String, String> shared = new HashMap<>();
optionMap.put("keyTab", "C:\\kerberos_files\\sapuser.keytab");
optionMap.put("principal", "HTTP/SAPTEST#EQSECTEST.LOCAL"); // default realm
// optionMap.put("principal", "kerberosuser"); // default realm
optionMap.put("useFirstPass", "true");
optionMap.put("doNotPrompt", "true");
optionMap.put("refreshKrb5Config", "true");
optionMap.put("useTicketCache", "false");
optionMap.put("renewTGT", "false");
optionMap.put("useKeyTab", "true");
optionMap.put("storeKey", "true");
optionMap.put("isInitiator", "true");
optionMap.put("useSubjectCredsOnly", "false");
optionMap.put("debug", "true"); // switch on debug of the Java implementation
krb5LoginModule.initialize(serviceUserSubject, null, shared, optionMap);
// login using details mentioned inside keytab
boolean loginOk = krb5LoginModule.login();
System.out.println("Login success: " + loginOk);
// This API adds Kerberos Credentials to the the Subject's private credentials set
boolean commitOk = krb5LoginModule.commit();
}
System.out.println("Principal from subject: " + serviceUserSubject.getPrincipals()); // this must display name of user to which the keytab corresponds to
Subject clientSubject = getClientContext(serviceUserSubject, kerberosTicket);
System.out.println("Client Subject-> " + clientSubject);
System.out.println("Client principal-> "+clientSubject.getPrincipals().toArray()[0]);
return clientSubject;
}
// Completes the security context initialisation and returns the client name.
private Subject getClientContext(Subject subject, final byte[] kerberosTicket) throws PrivilegedActionException {
Subject clientSubject = Subject.doAs(subject, new KerberosValidateAction(kerberosTicket));
return clientSubject;
}
private class KerberosValidateAction implements PrivilegedExceptionAction<Subject> {
byte[] kerberosTicket;
public KerberosValidateAction(byte[] kerberosTicket) {
this.kerberosTicket = kerberosTicket;
}
#Override
public Subject run() throws Exception {
GSSManager gssManager = GSSManager.getInstance();
GSSContext context = gssManager.createContext((GSSCredential) null);
Oid kerberosOid = new Oid("1.2.840.113554.1.2.2");
// context.requestCredDeleg(true); // needed when we are demanding ticket from KDC. In our scenario, we are getting ticket from browser(client)
// Called by the context acceptor upon receiving a token from the peer. This is our context acceptor
// This method may return an output token which the application will need to send to the peer for further processing by its initSecContext call.
// We will only accept the incoming token from Peer (browser) and fwd it to third party system
while (!context.isEstablished()) {
byte[] nextToken = context.acceptSecContext(kerberosTicket, 0, kerberosTicket.length);
}
boolean established = context.isEstablished();
String user = context.getSrcName().toString();
String serviceAccnt = context.getTargName().toString();
//check if the credentials can be delegated
if (!context.getCredDelegState()) {
System.out.println("credentials can not be delegated!");
return null;
}
//get the delegated credentials from the calling peer...
GSSCredential clientCred = context.getDelegCred();
//Create a Subject out of the delegated credentials.
//With this Subject the application server can impersonate the client that sent the request.
Subject clientSubject = GSSUtil.createSubject(context.getSrcName(), clientCred);
return clientSubject; // ***this contains only principal name and not credentials !!
}
}
I figured out one important point, which I didn't find anywhere documented. -
While configuring the kerberos service user in AD, "Trust this user for delegation (Kerberos Only)" will come in effect only for new TGTs which are created after this change is saved.
For example, if the user is logged in on client machine A, say at 10:00 AM.
Administrator enables the delegation to kerberos service user at 10:30 AM.
When the user hits an application URL from browser on machine A, the delegation will not work because his TGT is created before the delegation is turned on (at 10:00 AM).
If user logs out from the machine A and relogins, a new TGT will be generated. Delegation works perfectly for this user now.
I currently face the problem that an LDAP-Query is passed to an LDAP-Server and the LDAP-Server does not deliver results.
The query: (&(objectCategory=user)(mail=tester#oop-expert.de))
The given E-Mail is meant to not be found. So an empty result is expected.
In most environment configurations this query will pass perfectly and the LDAP returns an empty result immediately.
I broke down the problem to something that may relate to the network or host from where the query is sent. So the query will be fine if sent from one host/network and from another host/network the LDAP-Server will "starve" my LDAP-Client so the LDAP-Client closes the connection for a client-side timeout.
On the other hand: searching for an email that exists will always lead to an immediate result. Doesn't matter from which host/network.
The LDAP-Server is an Active Directory. There a several domain controllers providing an LDAP-Service, configured "round robin". Access per ip or dns does not make any difference on this subject.
The communication is secured via ssl. (ldaps)
In all situations the connection was established. So authentication and passing the query to the LDAP went fine.
Authorization should not be an issue either. I used the same LDAP user for all situations.
The LDAP client is always a JAVA implementation using InitialContext.
private InitialDirContext createDirContext(String principal, String credentials) throws NamingException {
if (credentials == null || credentials.isEmpty()) {
throw new LDAPLoginException();
}
return new InitialDirContext(createEnvironment(principal, credentials));
}
private Hashtable<String, String> createEnvironment(String principal, String credentials) {
Hashtable<String, String> env = new Hashtable<>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, this.ldapUrl);
// To get rid of the PartialResultException when using Active Directory
env.put(Context.REFERRAL, "follow");
// Needed for the Bind (User Authorized to Query the LDAP server)
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, principal);
env.put(Context.SECURITY_CREDENTIALS, credentials);
return env;
}
The code that builds and executes the query:
private LDAPUser getLDAPUserInfoByUniqueField(String attr, String value) {
DirContext serviceUserContext = null;
NamingEnumeration<SearchResult> results = null;
try {
String searchString = "(&(objectCategory=user)(" + attr + "=" + value + "))";
serviceUserContext = createDirContext(this.serviceUserPrincipal, serviceUserCredentials);
results = serviceUserContext.search("", searchString, createSearchControls()); // blocking...
return createLDAPUserInfo(results);
} catch (LDAPLoginException e) {
throw e;
} catch (Exception e) {
throw new LDAPFatalException(e);
} finally {
try {
if (results != null)
results.close();
} catch (NamingException e) {
}
try {
if (serviceUserContext != null)
serviceUserContext.close();
} catch (NamingException e) {
}
}
}
Utility methods:
private LDAPUser createLDAPUserInfo(NamingEnumeration<SearchResult> results) throws NamingException {
LDAPUser ldapUserInfo = null;
if (results.hasMore()) { // blocking here
SearchResult result = (SearchResult) results.next();
String sAMAccountName = extractsAMAccountName(result);
String distinguishName = extractDistinguishName(result);
String department = extractDepartment(result);
String email = extractEmail(result);
ldapUserInfo = new LDAPUser(sAMAccountName, distinguishName, department, email);
}
return ldapUserInfo;
}
private String extractsAMAccountName(SearchResult result) throws NamingException {
Attributes attrs = result.getAttributes();
Attribute attr = attrs.get("sAMAccountName");
return (String) attr.get();
}
The exception:
de.oopexpert.business.ldap.LDAPFatalException: javax.naming.PartialResultException [Root exception is javax.naming.CommunicationException: oopexpert.de:636 [Root exception is java.net.ConnectException: Connection timed out]]
at de.oopexpert.business.ldap.impl.LDAPImpl.getLDAPUserInfoByUniqueField(LDAPImpl.java:90)
at de.oopexpert.business.ldap.impl.LDAPImpl.getLDAPUserInfoByEmail(LDAPImpl.java:57)
Caused by: javax.naming.PartialResultException [Root exception is javax.naming.CommunicationException: oopexpert.de:636 [Root exception is java.net.ConnectException: Connection timed out]]
at com.sun.jndi.ldap.LdapNamingEnumeration.hasMoreImpl(LdapNamingEnumeration.java:242)
at com.sun.jndi.ldap.LdapNamingEnumeration.hasMore(LdapNamingEnumeration.java:189)
at de.oopexpert.business.ldap.impl.LDAPImpl.createLDAPUserInfo(LDAPImpl.java:139)
at de.oopexpert.business.ldap.impl.LDAPImpl.getLDAPUserInfoByUniqueField(LDAPImpl.java:84)
Any hints?
We struggled with four persons what is going on. We figured out, that it has something to do with "name resolution problems" on the server side when "following refferals". The customer specific environment configuration inherently prohibits us to prevent this behaviour. So we came up with a workaround.
As we are operating in an active directory domain we have global catalog servers. The important statement to this is from Microsoft "technet":
The global catalog is a distributed data repository that contains a searchable, partial representation of every object in every domain in a multidomain Active Directory Domain Services (AD DS) forest. The global catalog is stored on domain controllers that have been designated as global catalog servers and is distributed through multimaster replication. Searches that are directed to the global catalog are faster because they do not involve referrals to different domain controllers.
(from What Is the Global Catalog?)
The phrase "do not involve refferals" leads us to my JNDI environment configuration where I set the following:
env.put(Context.REFERRAL, "follow");
So as I did that, the LDAP-Server will ask other LDAP-Servers if the result of a query seems to be incomplete. This was confirmed by our administrators when I started the query and they debugged it on TCP-Level.
Somehow, I maybe do not really represent it correctly, the name of the other LDAP-Server could not be resolved which leads the client to starvation as the first LDAP-Server waits for resolution.
We tried to omit the client parameter "Context.Refferals=follow". Here we get immediate response. But the response wasn't as expected:
javax.naming.PartialResultException: Unprocessed Continuation Reference(s); remaining name ''
This came up because the first LDAP-Server has the opinion to return incomplete data.
Our administrators say that this can never be the case, because "every domain controller" is a "Global Catalog Server".
So my workaround is: I will deal with this exception and interprete it as "no result".
I have a very simple LDAP auth :
DirContext ctx = null;
try
{
logger.debug("Trying to log with LDAP");
Hashtable<String, String> env = new Hashtable<String, String>();
env.put(javax.naming.Context.INITIAL_CONTEXT_FACTORY, ninjaProperties.get("ldapFactory"));
env.put(javax.naming.Context.PROVIDER_URL, ninjaProperties.get("ldapProvider"));
env.put(javax.naming.Context.SECURITY_AUTHENTICATION, ninjaProperties.get("ldapAuthentication"));
env.put(javax.naming.Context.SECURITY_PRINCIPAL, ninjaProperties.get("ldapDomain") + context.getParameter("login"));
env.put(javax.naming.Context.SECURITY_CREDENTIALS, context.getParameter("password"));
ctx = new InitialDirContext(env);
if(ctx != null){
logger.info( "User [" + context.getParameter("login") + "] logged in successfully." );
ctx.close();
return Results.redirect("/TermeController/consult");
}
}
catch (Exception e)
{
logger.error("LDAP Login failed : " + e.toString());
}
For now, users logg in with a form. I would like to set up an SSO auth, but i can't find a way to set it up easily.
Do i need to use something like CAS or so? Or is there a simple way?
Thx :)
SSO is quite a broad topic, I am not sure what exactly you are trying to achieve here. However if you want SSO for windows users i.e. allowing Windows users that are already authenticated against a domain controller to access your app with the same credentials you could look into WAFFLE which does exactly that.
I have implemented method:
public static LdapContext buildContext(String username, String password) {
LdapContext context = null;
Hashtable<String, String> env = new Hashtable<String, String>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, username);
env.put(Context.SECURITY_CREDENTIALS, password);
env.put(Context.PROVIDER_URL, DOMAIN_URL);
try {
context = new InitialLdapContext(env, null);
} catch (NamingException e) {
}
return context;
}
I do not know the full CN/DN string. I only pass the name of a user (f.e. Tom) and password.
I have no info about groups etc..
Thank you in advance!
Search for the entry using what information you have. The search result will contain the number of entries that matched the search and the DN of each entry that matched, therefore the search should be as restrictive as possible in order to return just the one entry for which you're looking. Then use that DN to BIND to the server,
What Terry said.
We have an example of performing Basic JNDI Search with Administration Account
I'm using the following java code to authenticate an LDAP user. This will succeed if the object exists with the specified username and password. However, I don't see any properties on the user which would allow me to activate/deactivate the user. Specifically, I just want to set a property on the user which would make authentication fail without actually deleting the entire object.
Can this be done directly or do I need to write separate code which loads the user and checks that a field is marked active or inactive?
public static boolean authenticate(String url, String securityAuthentication, String securityPrincipal, String usernameKey, String username, String password) {
try {
Hashtable env = new Hashtable();
env.put(InitialContext.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory");
env.put(InitialContext.PROVIDER_URL, url);
env.put(InitialContext.SECURITY_AUTHENTICATION, securityAuthentication);
env.put(InitialContext.SECURITY_PRINCIPAL, usernameKey +"="+ username + securityPrincipal);
env.put(InitialContext.SECURITY_CREDENTIALS, password );
// Create the initial context
DirContext ctx = new InitialDirContext(env);
return true;
} catch (NamingException e) {
return false;
}
}
EDIT: I'm told that in we have one that's active directory and another which is 389 Directory Server.