I had a service which tries to use kerberos ticket cache but I always get error like
Caused by: java.lang.RuntimeException: GSSException: No valid credentials provided (Mechanism level: Failed to find any Kerberos credentails).
If I do not use ticket caching, everything seems just works.
Below is the parameters I use to specify ticket caching.
loginContext = new LoginContext("", null, null, new Configuration()
{
#Override
public AppConfigurationEntry[] getAppConfigurationEntry(String name)
{
Map<String, String> options = new HashMap<>();
options.put("refreshKrb5Config", "true");
options.put("doNotPrompt", "true");
if (LOG.isDebugEnabled()) {
options.put("debug", "true");
}
if (config.getKeytab() != null) {
options.put("keyTab", config.getKeytab().getAbsolutePath());
}
options.put("isInitiator", "false");
options.put("useKeyTab", "true");
options.put("principal", servicePrincipal);
//options.put("storeKey", "true");
//manually specify /tmp/krb5cc_ServiceUid for ticketCache
options.put("ticketCache", config.getCredentialCache().getAbsolutePath());
options.put("useTicketCache", "true");
options.put("renewTGT", "true");
return new AppConfigurationEntry[] {new AppConfigurationEntry(Krb5LoginModule.class.getName(), REQUIRED, options)};
}
});
loginContext.login();
Then I use below code snippets to get the credential:
String name = loginContext.getSubject().getPrincipals().iterator().next().getName();
serverCredential = doAs(loginContext.getSubject(), () -> gssManager.createCredential(
//gssManager.createName(config.getServiceName() + "#" + hostname, GSSName.NT_HOSTBASED_SERVICE),
gssManager.createName(name, GSSName.NT_HOSTBASED_SERVICE),
//INDEFINITE_LIFETIME,
DEFAULT_LIFETIME,
/*new Oid[] {
new Oid("1.2.840.113554.1.2.2"), // kerberos 5
new Oid("1.3.6.1.5.5.2") // spnego
},*/
new Oid("1.2.840.113554.1.2.2"),
ACCEPT_ONLY));
Is there anything wrong with above code snippets? I also confirmed that /tmp/krb5cc_ServiceUid indeed exists.
Thank you
Related
I'm adding LDAP authentication to the spring-boot application. All set accordingly and I'm getting "LDAP error code 49 AcceptSecurityContext error data 52e v2580" error even after providing correct credentials.
I'm using import javax.naming.Context; and have mentioned the code below.
String url = ldap_url;
String domain = ldap_domain;
String uname = request.getUsername();
String pwd = request.getPassword();
boolean authentication = false;
boolean error = true;
String msg;
String ldapSearchBase = "OU=TEST_OU, DC=DC2, DC=DC1";
// create env for initial context
Hashtable<String, String> env = new Hashtable<String, String>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, url);
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, "CN=" + uname + "#" + domain + "," + ldapSearchBase);
env.put(Context.SECURITY_CREDENTIALS, pwd);
NamingEnumeration results = null;
try {
LdapContext ctx = new InitialLdapContext(env, null);
authentication = true;
error = false;
} catch (NamingException e) {
logger.error("LDAP error for :{NamingException}" + e);
return ResponseEntity.ok(new ApiResponse(true, e.getMessage()));
} finally {
if (!error) {
msg = "Login success!!!";
} else {
msg = "Authentication failed!";
}
}
logger.info("exitinig...");
if (authentication) {
return ResponseEntity.ok(new ApiResponse(false, msg));
} else {
return ResponseEntity.ok(new ApiResponse(true, msg));
}
Error is catching as NamingException.
An error response with LDAP error code 49 ... data 52e "Returns when username is valid but password/credential is invalid."
There could be infrastructure issues such as when the domain controller computer account may not be synchronized with the Key Distribution Center (KDC). However you would probably have a lot more other issues when this condition exists.
This can occur when your account requires a SmartCard to sign in.
There is a setting in Active Directory called "Smart card is required for interactive logon".
You might get this error in Active directory (Windows popular implementation of LDAP) if you specify the username but not the windows domain.
This is what you should do:
USERNAME: <YOUR_WINDOWS_DOMAIN>\<YOUR_USERNAME>
PASSWORD: <YOUR_PASSWORD>
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 am trying to invoke EJB but get SASL PROPERTY: No such filed error. I have added SASL jar "jboss-sasl-1.0.0.Beta1.jar" as a dependency too.
This is my code:
Properties properties = new Properties();
properties.put("endpoint.name", "client-endpoint");
properties.put("remote.connectionprovider.create.options.org.xnio.Options.SSL_ENABLED", "false");
properties.put("remote.connections", "default");
properties.put("remote.connection.default.host", "localhost");
properties.put("remote.connection.default.port", "4447");
properties.put("remote.connection.default.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS", "false");
properties.put("remote.connection.default.username", "user");
properties.put("remote.connection.default.password", "pass");
EJBClientConfiguration cc = new PropertiesBasedEJBClientConfiguration(properties);
final ContextSelector<EJBClientContext> ejbClientContextSelector = new ConfigBasedEJBClientContextSelector(cc);
final ContextSelector<EJBClientContext> previousSelector = EJBClientContext.setSelector(ejbClientContextSelector);
StatelessEJBLocator<SomeClass> locator = new StatelessEJBLocator(SomeClass.class, "app", "module", "viewType", "distinctName");
SecurityDomainService ejb = org.jboss.ejb.client.EJBClient.createProxy(locator);`
But I get this exception:
java.lang.NoSuchFieldError: SASL_PROPERTIES
org.jboss.ejb.client.PropertiesBasedEJBClientConfiguration.<clinit>(PropertiesBasedEJBClientConfiguration.java:79)
Can anyone help?
It turns out I was getting this error because I wasn't using the correct EJB Client jar.
I guess jboss-ejb-client-1.0.16.Final.jar introduced this field, and it works fine when used with this jar.
I am totally new to this field and not much experienced in java too. I was assigned this task and I could connect in simple mode as admin and retrieve info, but couldnot reset password. I found in many sites that I have to use ssl for that, but couldnt successfully do that as I get a
"simple bind failed"
error I post my code below of what I did and also the codes I commented out(which I tried earlier). Please please please help. I couldnt solve the problem from any source I received. I used a certificate copied from server into my keystore. Is this the right way to use that?? If i remove the ssl part
env.put(Context.SECURITY_PROTOCOL,"ssl");
I get a handshake exception
Problem with TLS: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path validation failed: java.security.cert.CertPathValidatorException: signature check failed
public class ActiveDirectory {
private DirContext ctx;
public boolean connect(String username,String password){
Hashtable<String, String> env = new Hashtable<String, String>();
// Properties env=new Properties();
env.put(Context.SECURITY_PROTOCOL,"ssl");
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.PROVIDER_URL, "ldap://192.168.1.199:389");
env.put(Context.REFERRAL, "follow");
// The value of Context.SECURITY_PRINCIPAL must be the logon username
// with the domain name
env.put(Context.SECURITY_PRINCIPAL, username+"#xxxx.net");
// The value of the Context.SECURITY_CREDENTIALS should be the user's
// password
env.put(Context.SECURITY_CREDENTIALS, password);
try {
// Authenticate the logon user
ctx = new InitialLdapContext(env,null);
return true;
}catch(NamingException e){
System.out.println("Error in connecting : " + e.getMessage());
return false;
}
}
public boolean changePasswordAdmin(String userName,String newPassword){
try {
//set password is a ldap modfy operation
//Secure the session with TLS
StartTlsResponse tls = (StartTlsResponse)((LdapContext) ctx).extendedOperation(new StartTlsRequest());
tls.negotiate();
//set password is a ldap modfy operation
ModificationItem[] mods = new ModificationItem[1];
//Replace the "unicdodePwd" attribute with a new value
//Password must be both Unicode and a quoted string
String newQuotedPassword = "\"" + newPassword + "\"";
byte[] newUnicodePassword = newQuotedPassword.getBytes("UTF-16LE");
mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("unicodePwd", newUnicodePassword));
// Perform the update
ctx.modifyAttributes(userName, mods);
System.out.println("Reset Password for: " + userName);
tls.close();
ctx.close();
return true;
}
catch (NamingException e) {
System.out.println("Problem resetting password: " + e);
}
catch (UnsupportedEncodingException e) {
System.out.println("Problem encoding password: " + e);
}
catch (IOException e) {
System.out.println("Problem with TLS: " + e);
}
return false;
}
public static void main(String args[]) throws NamingException {
Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider());
// the keystore that holds trusted root certificates
System.setProperty("javax.net.ssl.trustStore", "C:\\keystore.jks");
System.setProperty("javax.net.ssl.trustStorePassword", "****");
System.setProperty("javax.net.ssl.keyStore", "C:\\keystore.jks");
System.setProperty("javax.net.ssl.keyStorePassword", "****");
ActiveDirectory d= new ActiveDirectory();
d.connect("Administrator", "Group&Team2");
System.out.println(d.fetchData("MG"));
System.out.println(d.changePasswordAdmin("CN=Manager MG. Manager,OU=Manager,DC=xxxxx,DC=net", "Abcd#10"));
}
}
Your active directory doesn't have a valid certificate.
This is probably the case because the root certificate wasn't imported in Java.
Here is a small tutorial how to import certificates into java.
public static void main(String[] args)
{
String INITCTX = "com.sun.jndi.ldap.LdapCtxFactory";
String MY_HOST = "ldap://Localhost:1389";
String MGR_DN = "cn=John,ou=Users,o=IT,dc=QuizPortal";
String MGR_PW = "password";
//Identify service provider to use
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY, INITCTX);
env.put(Context.PROVIDER_URL, MY_HOST);
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, MGR_DN);
env.put(Context.SECURITY_CREDENTIALS, MGR_PW);
try
{
// Create the initial directory context
InitialDirContext initialContext = new InitialDirContext(env);
System.out.println("Context Sucessfully Initialized");
}
catch(Exception e)
{
System.err.println(e);
}
}
I would like to ask when I set the MGR_DN = "cn=John,ou=Users,o=IT,dc=QuizPortal" to MGR_DN = "uid=103,ou=Users,o=IT,dc=QuizPortal". Basically changing from cn to uid, I would encounter an error
javax.naming.AuthenticationException: [LDAP: error code 49 - Invalid Credentials]
I am authenticated when is specified as cn=John but not uid=103. Am I not allowed to specify by uid?
If you don't know the exact DN in advance, you should do a search in the LDAP directory first. This can be done more or less like this (make sure you catch the relevant exceptions):
Properties env = new Properties();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, ldapServerUrl);
env.put(Context.SECURITY_AUTHENTICATION, "none");
SearchControls searchCtrls = new SearchControls();
searchCtrls.setReturningAttributes(new String[] {});
searchCtrls.setSearchScope(SearchControls.SUBTREE_SCOPE);
String filter = "(&(cn=" + identifier + "))";
DirContext ctx = null;
ctx = new InitialDirContext(env);
NamingEnumeration<SearchResult> answer = ctx.search(
ldapBaseDN, filter, searchCtrls);
String fullDN = null;
if (answer.hasMore()) {
fullDN = answer.next().getNameInNamespace();
ctx.close();
ctx = null;
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, fullDN);
env.put(Context.SECURITY_CREDENTIALS, password);
ctx = new InitialDirContext(env);
return true;
}
// Exception otherwise ...
Here, the search filter is "(&(cn=" + identifier + "))" (so, for example (&(cn=John))), but you could use the uid instead. Uniqueness of the results depends on the configuration of the LDAP server. The base DN also depends on the way it's set up (it could be ou=Users,o=IT,dc=QuizPortal in your example).
You have to specify the DN or distinguished name. That's the name the user is bound as in the directory. You can't just select any chain of attributes. If your users are bound via the 'cn' attribute then only the 'cn' attribute is part of the DN.
It looks like a server configuration issue. Here's a similar problem including a solution. Basically you'll have to specify whether to use uid or cn for authentication in ldap-authentication.properties.