diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java b/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java index b518c45e9..f90e7bea2 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java @@ -127,19 +127,33 @@ private void intAuthInit() throws SQLServerException { // Kerberos OID Oid kerberos = new Oid("1.2.840.113554.1.2.2"); 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 941d71780..bdb38e3e1 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()); } } - } - }