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.
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 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.
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'm beginner,
I'm writing a program which logins into my g-mail accounts and checks the messages in the Inbox.
To do this I'm using imaps ("imaps.gmail.com").
But what my problem is while logging into all the accounts, checking the messages in it one by one.
Program Reporting an error/kind of:
javax.mail.AuthenticationFailedException: [ALERT] Please log in via
your web browser: some url (Failure) imaps: xxxx#gmail.com for the
second and other accounts...
From this What I understand is I'm not closing sessions, store properly. So,Program is unable to check in the respective second and rest mail accounts..
Someone Help (With clarification)..
And the architecture of my code:
public class InboxRead {
public static boolean release = false;
private Session session = null;
public void checkInbox(String username, String password) {
// logging into account using imaps..
Properties props = System.getProperties();
props.setProperty("mail.store.protocol", "imaps");
session = Session.getDefaultInstance(props, null);
store = session.getStore("imaps");
store.connect("imap.gmail.com", username, password);
System.out.println(store);
// reading inbox... closing all folders here
store.close();
session = null;
release = true;
}
public static void main(String[] args) {
for(;;) { // gives no of accounts with credentials..
do {
String username = "xxx#gmail.com";
String password = "xxxx";
InboxRead.checkInbox(username, password);
} while(release);
}
}
}
...
Its a Google gateway thrown error. Google maintain high security in the aspect of profile access or even mail access but even that can be controlled by the user using below link Google secure control
go to that link with your google account and select "turn on" and then try to execute your program.
We have a static method in a utility class that will download a file from a URL. An authenticator has been set up so that if a username and password is required, the credentials can be retrieved. The problem is that the credentials from the first successful connection are used for every connection afterwords, as long as the credentials are valid. This is a problem because our code is multi user, and since the credentials are not checked for every connection, it's possible that a user without proper credentials could download a file.
Here's the code we're using
private static URLAuthenticator auth;
public static File download(String url, String username, String password, File newFile)
{
auth.set(username, password);
Authenticator.setDefault(auth);
URL fURL = new URL(url);
OutputStream out = new BufferedOutputStream(new FileOutputStream(newFile));
URLConnection conn = fURL.openConnection();
InputStream in = conn.getInputStream();
try
{
copyStream(in, out);
}
finally
{
if (in != null)
in.close();
if (out != null)
out.close();
}
return newFile;
}
public class URLAuthenticator extends Authenticator
{
private String username;
private String password;
public URLAuthenticator(String username, String password)
{
set(username, password);
}
public void set(String username, String password)
{
this.username = username;
this.password = password;
}
protected PasswordAuthentication getPasswordAuthentication()
{
log.debug("Retrieving credentials '" + username + "', '" + password + "'.");
return new PasswordAuthentication(username, password.toCharArray());
}
}
I only see the log statement from getPasswordAuthentication once, the first time that a file is downloaded. After that first successful attempt, getPasswordAuthentication is not called again, even though the credentials have been reset. The result is that after the first successful connection, invalid credentials can be entered, and a successful connection can still be made. Is this possibly a result of the download method being static, and in a static class?
Edit
I forgot to mention that this is in a JSF webapp running under tomcat - maybe one of those technologies is setting some default credentials somewhere?
I've pulled the URLAuthenticator out into its own class, and made it as non-static as possible, but the problem still exists. I've read that if the default authenticator is set to null with Authenticator.setDefault(null), then on windows the NTLM authentication will be used. That shouldn't be the problem here since I'm setting the Authenticator everytime, but I thought I'd throw it out there. The NTLM authentication is definately getting used, because if the server is run as a user that has access to the downloaded file, the credentials aren't even asked for, the file just downloads. So something obviously is grabbing my credentials and passing them in before the authenticator is called.
I've figured something out at least. It appears that this behavior is a bug. A workaround is to use a Sun specific class to explicitly reset the cache, like so:
import sun.net.www.protocol.http.AuthCacheValue;
import sun.net.www.protocol.http.AuthCacheImpl;
....
AuthCacheValue.setAuthCache(new AuthCacheImpl());
Authenticator.setDefault(new URLAuthenticator(username, password));
I'm resetting the AuthCache at the top of the download function described in the question. During compile, you'll get warnings about using these classes. This doesn't completely fix the problem: if NTLM authentication works, the authenticator still won't get called, but as long as the server is running under a user that has does not have permission for the requested file, this should clear the cache out.
Facing the same problem, none of these answers worked for me. It took me some time and looking through the java runtime source to figure this out.
Sun.net.www.protocol.http.ntlm.NTLMAuthentication attempts to use transparent authentication, what is basically use of current user credentials to login to remote server. In my server to server scenario (Java EE server to Sharepoint) this wasn't acceptable.
In order to disable transparent authentication we need to let authentication provider know that connection is not trusted and it needs to authenticate with every call:
static {
NTLMAuthenticationCallback.setNTLMAuthenticationCallback(new NTLMAuthenticationCallback()
{
#Override
public boolean isTrustedSite(URL url)
{
return false;
}
});
}
Here is the working( i.e., getPasswordAuthentication() called for every request) code. You will see compilation warning which can be ignored.
static class MyCache implements sun.net.www.protocol.http.AuthCache{
public void put(String pkey, sun.net.www.protocol.http.AuthCacheValue value){
}
public sun.net.www.protocol.http.AuthCacheValue get(String pkey, String skey){
return null;
}
public void remove(String pkey, sun.net.www.protocol.http.AuthCacheValue entry){
}
}
static{
sun.net.www.protocol.http.AuthCacheValue.setAuthCache(new MyCache());
}
It looks like in your finally block you need to call
Authenticator.setDefault(null);