diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerDataSource.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerDataSource.java index 6c74e6279b..443f8c75dd 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerDataSource.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerDataSource.java @@ -505,6 +505,21 @@ public interface ISQLServerDataSource extends javax.sql.CommonDataSource { */ String getAuthentication(); + /** + * Sets the realm for Kerberos authentication. + * + * @param realm + * A String that contains the realm + */ + void setRealm(String realm); + + /** + * Returns the realm for Kerberos authentication. + * + * @return A String that contains the realm + */ + String getRealm(); + /** * Sets the server spn. * diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java index 1720fb2c7d..26d96805f8 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java @@ -581,6 +581,22 @@ public String getServerName() { return getStringProperty(connectionProps, SQLServerDriverStringProperty.SERVER_NAME.toString(), null); } + /** + * Sets the realm for Kerberos authentication. + * + * @param realm + * realm + */ + @Override + public void setRealm(String realm) { + setStringProperty(connectionProps, SQLServerDriverStringProperty.REALM.toString(), realm); + } + + @Override + public String getRealm() { + return getStringProperty(connectionProps, SQLServerDriverStringProperty.REALM.toString(), null); + } + /** * Sets the Service Principal Name (SPN) of the target SQL Server. * https://msdn.microsoft.com/en-us/library/cc280459.aspx diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java index 3d307292c2..34879c77df 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java @@ -349,6 +349,7 @@ enum SQLServerDriverStringProperty { DOMAIN("domain", ""), SERVER_NAME("serverName", ""), SERVER_SPN("serverSpn", ""), + REALM("realm", ""), SOCKET_FACTORY_CLASS("socketFactoryClass", ""), SOCKET_FACTORY_CONSTRUCTOR_ARG("socketFactoryConstructorArg", ""), TRUST_STORE_TYPE("trustStoreType", "JKS"), @@ -569,6 +570,8 @@ public final class SQLServerDriver implements java.sql.Driver { SQLServerDriverStringProperty.SERVER_NAME.getDefaultValue(), false, null), new SQLServerDriverPropertyInfo(SQLServerDriverStringProperty.SERVER_SPN.toString(), SQLServerDriverStringProperty.SERVER_SPN.getDefaultValue(), false, null), + new SQLServerDriverPropertyInfo(SQLServerDriverStringProperty.REALM.toString(), + SQLServerDriverStringProperty.REALM.getDefaultValue(), false, null), new SQLServerDriverPropertyInfo(SQLServerDriverStringProperty.SOCKET_FACTORY_CLASS.toString(), SQLServerDriverStringProperty.SOCKET_FACTORY_CLASS.getDefaultValue(), false, null), new SQLServerDriverPropertyInfo(SQLServerDriverStringProperty.SOCKET_FACTORY_CONSTRUCTOR_ARG.toString(), diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java index 327277e27c..f08cc5fc20 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java @@ -187,6 +187,7 @@ protected Object[][] getContents() { {"R_domainPropertyDescription", "The Windows domain to authenticate in using NTLM."}, {"R_serverNamePropertyDescription", "The computer running SQL Server."}, {"R_portNumberPropertyDescription", "The TCP port where an instance of SQL Server is listening."}, + {"R_realmPropertyDescription", "The realm for Kerberos authentication."}, {"R_serverSpnPropertyDescription", "SQL Server SPN."}, {"R_columnEncryptionSettingPropertyDescription", "The column encryption setting."}, {"R_enclaveAttestationUrlPropertyDescription", "The enclave attestation URL."}, diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SSPIAuthentication.java b/src/main/java/com/microsoft/sqlserver/jdbc/SSPIAuthentication.java index c559a147fc..c0bbe0d734 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SSPIAuthentication.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SSPIAuthentication.java @@ -9,6 +9,8 @@ import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Locale; +import java.util.logging.Level; +import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -31,6 +33,8 @@ abstract class SSPIAuthentication { */ private static final Pattern SPN_PATTERN = Pattern.compile("MSSQLSvc/(.*):([^:@]+)(@.+)?", Pattern.CASE_INSENSITIVE); + + private static final Logger logger = Logger.getLogger("com.microsoft.sqlserver.jdbc.SSPIAuthentication"); /** * Make SPN name @@ -124,7 +128,7 @@ private String findRealmFromHostname(RealmValidator realmValidator, String hostn * flag to indicate of hostname canonicalization is allowed * @return SPN enriched with realm */ - String enrichSpnWithRealm(String spn, boolean allowHostnameCanonicalization) { + String enrichSpnWithRealm(SQLServerConnection con, String spn, boolean allowHostnameCanonicalization) { if (spn == null) { return spn; } @@ -132,26 +136,33 @@ String enrichSpnWithRealm(String spn, boolean allowHostnameCanonicalization) { if (!m.matches()) { return spn; } - if (m.group(3) != null) { + String realm = con.activeConnectionProperties.getProperty(SQLServerDriverStringProperty.REALM.toString()); + if (m.group(3) != null && (null == realm || realm.trim().isEmpty())) { // Realm is already present, no need to enrich, the job has already been done return spn; } String dnsName = m.group(1); String portOrInstance = m.group(2); - RealmValidator realmValidator = getRealmValidator(); - String realm = findRealmFromHostname(realmValidator, dnsName); - if (realm == null && allowHostnameCanonicalization) { - // We failed, try with canonical host name to find a better match - try { - String canonicalHostName = InetAddress.getByName(dnsName).getCanonicalHostName(); - realm = findRealmFromHostname(realmValidator, canonicalHostName); - // match means hostname is correct (for instance if server name was an IP) so override dnsName as well - dnsName = canonicalHostName; - } catch (UnknownHostException e) { - // ignored, cannot canonicalize + // If realm is not specified in the connection, try to derive it. + if (null == realm || realm.trim().isEmpty()) { + RealmValidator realmValidator = getRealmValidator(); + realm = findRealmFromHostname(realmValidator, dnsName); + if (null == realm && allowHostnameCanonicalization) { + // We failed, try with canonical host name to find a better match + try { + String canonicalHostName = InetAddress.getByName(dnsName).getCanonicalHostName(); + realm = findRealmFromHostname(realmValidator, canonicalHostName); + // match means hostname is correct (for instance if server name was an IP) so override dnsName as well + dnsName = canonicalHostName; + } catch (UnknownHostException e) { + // ignored, cannot canonicalize + if (logger.isLoggable(Level.FINER)) { + logger.finer("Could not canonicalize host name. " + e.toString()); + } + } } } - if (realm == null) { + if (null == realm) { return spn; } else { StringBuilder sb = new StringBuilder("MSSQLSvc/"); @@ -188,6 +199,6 @@ String getSpn(SQLServerConnection con) { spn = makeSpn(con, con.currentConnectPlaceHolder.getServerName(), con.currentConnectPlaceHolder.getPortNumber()); } - return enrichSpnWithRealm(spn, null == userSuppliedServerSpn); + return enrichSpnWithRealm(con, spn, null == userSuppliedServerSpn); } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java index 405917fe17..2f6486faf2 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java @@ -189,6 +189,9 @@ public void testDataSource() { ds.setServerName(stringPropValue); assertEquals(stringPropValue, ds.getServerName(), TestResource.getResource("R_valuesAreDifferent")); + ds.setRealm(stringPropValue); + assertEquals(stringPropValue, ds.getRealm(), TestResource.getResource("R_valuesAreDifferent")); + ds.setServerSpn(stringPropValue); assertEquals(stringPropValue, ds.getServerSpn(), TestResource.getResource("R_valuesAreDifferent"));