From 290b8bc437dec25f7688d9c457e86f21021524f9 Mon Sep 17 00:00:00 2001
From: Pierre Souchay
Date: Tue, 28 Feb 2017 10:42:54 +0100
Subject: [PATCH 1/3] Allow authenticating using Kerberos and a
Principal/Password.
This patch allow to authenticate using kerberos using the previous
methods (eg: keytab) or specifying user/password either in properties
or in connect method.
This allows to use GUIs for instance to connect using Kerberos without
having a sql user.
---
.../sqlserver/jdbc/KerbAuthentication.java | 5 +-
.../sqlserver/jdbc/KerbCallback.java | 53 +++++++++++++++++++
2 files changed, 56 insertions(+), 2 deletions(-)
create mode 100644 src/main/java/com/microsoft/sqlserver/jdbc/KerbCallback.java
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java b/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java
index d84058e58f..d3fb4aa94b 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java
@@ -87,7 +87,6 @@ class SQLJDBCDriverConfig extends Configuration {
else {
Map confDetails = new HashMap();
confDetails.put("useTicketCache", "true");
- confDetails.put("doNotPrompt", "true");
appConf = new AppConfigurationEntry("com.sun.security.auth.module.Krb5LoginModule",
AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, confDetails);
if (authLogger.isLoggable(Level.FINER))
@@ -144,14 +143,16 @@ private void intAuthInit() throws SQLServerException {
AccessControlContext context = AccessController.getContext();
currentSubject = Subject.getSubject(context);
if (null == currentSubject) {
- lc = new LoginContext(CONFIGNAME);
+ lc = new LoginContext(CONFIGNAME, new KerbCallback(con));
lc.login();
// per documentation LoginContext will instantiate a new subject.
currentSubject = lc.getSubject();
}
}
catch (LoginException le) {
+ authLogger.fine("Failed to login due to " + le.getClass().getName() + ":" + le.getMessage());
con.terminate(SQLServerException.DRIVER_ERROR_NONE, SQLServerException.getErrString("R_integratedAuthenticationFailed"), le);
+ return;
}
if (authLogger.isLoggable(Level.FINER)) {
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/KerbCallback.java b/src/main/java/com/microsoft/sqlserver/jdbc/KerbCallback.java
new file mode 100644
index 0000000000..941d717800
--- /dev/null
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/KerbCallback.java
@@ -0,0 +1,53 @@
+package com.microsoft.sqlserver.jdbc;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Properties;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+
+public class KerbCallback implements CallbackHandler {
+
+ private final SQLServerConnection con;
+
+ KerbCallback(SQLServerConnection con) {
+ this.con = con;
+ }
+
+ private static String getAnyOf(Callback callback, Properties properties, String... names)
+ throws UnsupportedCallbackException {
+ for (String name : names) {
+ String val = properties.getProperty(name);
+ if (val != null && !val.trim().isEmpty()) {
+ return val;
+ }
+ }
+ throw new UnsupportedCallbackException(callback,
+ "Cannot get any of properties: " + Arrays.toString(names) + " from con properties");
+ }
+
+ @Override
+ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+ for (int i = 0; i < callbacks.length; i++) {
+ Callback callback = callbacks[i];
+ if (callback instanceof NameCallback) {
+ ((NameCallback) callback).setName(getAnyOf(callback, con.activeConnectionProperties,
+ "user", SQLServerDriverStringProperty.USER.name()));
+ } else if (callback instanceof PasswordCallback) {
+ String password = getAnyOf(callback, con.activeConnectionProperties,
+ "password", SQLServerDriverStringProperty.PASSWORD.name());
+ ((PasswordCallback) callbacks[i])
+ .setPassword(password.toCharArray());
+
+ } else {
+ throw new UnsupportedCallbackException(callback, "Unrecognized Callback type: " + callback.getClass());
+ }
+ }
+
+ }
+
+}
From c4082f81b1bf164d5acc608b1c66eff14b2c0a75 Mon Sep 17 00:00:00 2001
From: Pierre Souchay
Date: Tue, 28 Feb 2017 23:47:11 +0100
Subject: [PATCH 2/3] When Kerberos credentials were wrong, authentication was
looping for a long time.
This commit solves this by stopping directly authentication when login() fails.
It means that if keytab is wrong OR provided principal/password are wrong, the
driver immediatly return and does not retry in a endless loop.
---
.../sqlserver/jdbc/KerbAuthentication.java | 20 ++++++++++++++++---
.../sqlserver/jdbc/KerbCallback.java | 16 +++++++++++----
2 files changed, 29 insertions(+), 7 deletions(-)
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java b/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java
index d3fb4aa94b..6fdfd3cb5a 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java
@@ -139,19 +139,33 @@ private void intAuthInit() throws SQLServerException {
}
else {
Subject currentSubject = null;
+ KerbCallback callback = new KerbCallback(con);
try {
AccessControlContext context = AccessController.getContext();
currentSubject = Subject.getSubject(context);
if (null == currentSubject) {
- lc = new LoginContext(CONFIGNAME, new KerbCallback(con));
+ lc = new LoginContext(CONFIGNAME, callback);
lc.login();
// per documentation LoginContext will instantiate a new subject.
currentSubject = lc.getSubject();
}
}
catch (LoginException le) {
- authLogger.fine("Failed to login due to " + le.getClass().getName() + ":" + le.getMessage());
- con.terminate(SQLServerException.DRIVER_ERROR_NONE, SQLServerException.getErrString("R_integratedAuthenticationFailed"), le);
+ if (authLogger.isLoggable(Level.FINE)) {
+ authLogger.fine(toString() + "Failed to login using Kerberos due to " + le.getClass().getName() + ":" + le.getMessage());
+ }
+ try {
+ // Not very clean since it raises an Exception, but we are sure we are cleaning well everything
+ con.terminate(SQLServerException.DRIVER_ERROR_NONE, SQLServerException.getErrString("R_integratedAuthenticationFailed"), le);
+ } catch (SQLServerException alwaysTriggered) {
+ String message = String.format("%s due to %s (%s)", alwaysTriggered.getMessage(), le.getClass().getName(), le.getMessage());
+ if (callback.getUsernameRequested() != null) {
+ message = String.format("Login failed for Kerberos principal '%s'. %s", callback.getUsernameRequested(), message);
+ }
+ // By throwing Exception with LOGON_FAILED -> we avoid looping for connection
+ // In this case, authentication will never work anyway -> fail fast
+ throw new SQLServerException(message, alwaysTriggered.getSQLState(), SQLServerException.LOGON_FAILED, le);
+ }
return;
}
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/KerbCallback.java b/src/main/java/com/microsoft/sqlserver/jdbc/KerbCallback.java
index 941d717800..bdb38e3e1d 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/KerbCallback.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/KerbCallback.java
@@ -13,6 +13,7 @@
public class KerbCallback implements CallbackHandler {
private final SQLServerConnection con;
+ private String usernameRequested = null;
KerbCallback(SQLServerConnection con) {
this.con = con;
@@ -30,13 +31,22 @@ private static String getAnyOf(Callback callback, Properties properties, String.
"Cannot get any of properties: " + Arrays.toString(names) + " from con properties");
}
+ /**
+ * If a name was retrieved By Kerberos, return it.
+ * @return null if callback was not called or username was not provided
+ */
+ public String getUsernameRequested(){
+ return usernameRequested;
+ }
+
@Override
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
for (int i = 0; i < callbacks.length; i++) {
Callback callback = callbacks[i];
if (callback instanceof NameCallback) {
- ((NameCallback) callback).setName(getAnyOf(callback, con.activeConnectionProperties,
- "user", SQLServerDriverStringProperty.USER.name()));
+ usernameRequested = getAnyOf(callback, con.activeConnectionProperties,
+ "user", SQLServerDriverStringProperty.USER.name());
+ ((NameCallback) callback).setName(usernameRequested);
} else if (callback instanceof PasswordCallback) {
String password = getAnyOf(callback, con.activeConnectionProperties,
"password", SQLServerDriverStringProperty.PASSWORD.name());
@@ -47,7 +57,5 @@ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallback
throw new UnsupportedCallbackException(callback, "Unrecognized Callback type: " + callback.getClass());
}
}
-
}
-
}
From 115c7e9166b0393178a8276f624509aacbd0909d Mon Sep 17 00:00:00 2001
From: Pierre Souchay
Date: Fri, 7 Apr 2017 11:08:21 +0200
Subject: [PATCH 3/3] Reformat code in KerbCallback with formatter
Added translated message for Kerberos login authentication errors.
---
.../sqlserver/jdbc/KerbAuthentication.java | 7 +++--
.../sqlserver/jdbc/KerbCallback.java | 26 +++++++++----------
.../sqlserver/jdbc/SQLServerResource.java | 2 ++
3 files changed, 20 insertions(+), 15 deletions(-)
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java b/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java
index 6fdfd3cb5a..868b20d7eb 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java
@@ -13,6 +13,7 @@
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
+import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
@@ -158,9 +159,11 @@ private void intAuthInit() throws SQLServerException {
// Not very clean since it raises an Exception, but we are sure we are cleaning well everything
con.terminate(SQLServerException.DRIVER_ERROR_NONE, SQLServerException.getErrString("R_integratedAuthenticationFailed"), le);
} catch (SQLServerException alwaysTriggered) {
- String message = String.format("%s due to %s (%s)", alwaysTriggered.getMessage(), le.getClass().getName(), le.getMessage());
+ String message = MessageFormat.format(SQLServerException.getErrString("R_kerberosLoginFailed"),
+ alwaysTriggered.getMessage(), le.getClass().getName(), le.getMessage());
if (callback.getUsernameRequested() != null) {
- message = String.format("Login failed for Kerberos principal '%s'. %s", callback.getUsernameRequested(), message);
+ message = MessageFormat.format(SQLServerException.getErrString("R_kerberosLoginFailedForUsername"),
+ callback.getUsernameRequested(), message);
}
// By throwing Exception with LOGON_FAILED -> we avoid looping for connection
// In this case, authentication will never work anyway -> fail fast
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/KerbCallback.java b/src/main/java/com/microsoft/sqlserver/jdbc/KerbCallback.java
index bdb38e3e1d..8cdc4cca96 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/KerbCallback.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/KerbCallback.java
@@ -19,23 +19,24 @@ public class KerbCallback implements CallbackHandler {
this.con = con;
}
- private static String getAnyOf(Callback callback, Properties properties, String... names)
- throws UnsupportedCallbackException {
+ private static String getAnyOf(Callback callback,
+ Properties properties,
+ String... names) throws UnsupportedCallbackException {
for (String name : names) {
String val = properties.getProperty(name);
if (val != null && !val.trim().isEmpty()) {
return val;
}
}
- throw new UnsupportedCallbackException(callback,
- "Cannot get any of properties: " + Arrays.toString(names) + " from con properties");
+ throw new UnsupportedCallbackException(callback, "Cannot get any of properties: " + Arrays.toString(names) + " from con properties");
}
/**
* If a name was retrieved By Kerberos, return it.
+ *
* @return null if callback was not called or username was not provided
*/
- public String getUsernameRequested(){
+ public String getUsernameRequested() {
return usernameRequested;
}
@@ -44,16 +45,15 @@ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallback
for (int i = 0; i < callbacks.length; i++) {
Callback callback = callbacks[i];
if (callback instanceof NameCallback) {
- usernameRequested = getAnyOf(callback, con.activeConnectionProperties,
- "user", SQLServerDriverStringProperty.USER.name());
+ usernameRequested = getAnyOf(callback, con.activeConnectionProperties, "user", SQLServerDriverStringProperty.USER.name());
((NameCallback) callback).setName(usernameRequested);
- } else if (callback instanceof PasswordCallback) {
- String password = getAnyOf(callback, con.activeConnectionProperties,
- "password", SQLServerDriverStringProperty.PASSWORD.name());
- ((PasswordCallback) callbacks[i])
- .setPassword(password.toCharArray());
+ }
+ else if (callback instanceof PasswordCallback) {
+ String password = getAnyOf(callback, con.activeConnectionProperties, "password", SQLServerDriverStringProperty.PASSWORD.name());
+ ((PasswordCallback) callbacks[i]).setPassword(password.toCharArray());
- } else {
+ }
+ else {
throw new UnsupportedCallbackException(callback, "Unrecognized Callback type: " + callback.getClass());
}
}
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java
index 84bc5d3109..5d8c86d9bb 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java
@@ -380,5 +380,7 @@ protected Object[][] getContents() {
{"R_invalidFipsEncryptConfig", "Could not enable FIPS due to either encrypt is not true or using trusted certificate settings."},
{"R_invalidFipsProviderConfig", "Could not enable FIPS due to invalid FIPSProvider or TrustStoreType."},
{"R_serverPreparedStatementDiscardThreshold", "The serverPreparedStatementDiscardThreshold {0} is not valid."},
+ {"R_kerberosLoginFailedForUsername", "Cannot login with Kerberos principal {0}, check your credentials. {1}"},
+ {"R_kerberosLoginFailed", "Kerberos Login failed: {0} due to {1} ({2})"},
};
}