Skip to content

Commit

Permalink
Feature | Added support for Azure Active Directory Service Principal …
Browse files Browse the repository at this point in the history
…Authentication (#1456)

* Feature | Added support for AAD Service Principal Authentication

* Added tests for Service principal Authentication
  • Loading branch information
ulvii authored Nov 19, 2020
1 parent 0f257b8 commit 9f7b5c6
Show file tree
Hide file tree
Showing 11 changed files with 276 additions and 67 deletions.
1 change: 1 addition & 0 deletions src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ final class TDS {
static final byte ADALWORKFLOW_ACTIVEDIRECTORYPASSWORD = 0x01;
static final byte ADALWORKFLOW_ACTIVEDIRECTORYINTEGRATED = 0x02;
static final byte ADALWORKFLOW_ACTIVEDIRECTORYMSI = 0x03;
static final byte ADALWORKFLOW_ACTIVEDIRECTORYSERVICEPRINCIPAL = 0x01; // Using the Password byte as that is the closest we have.
static final byte FEDAUTH_INFO_ID_STSURL = 0x01; // FedAuthInfoData is token endpoint URL from which to acquire fed
// auth token
static final byte FEDAUTH_INFO_ID_SPN = 0x02; // FedAuthInfoData is the SPN to use for acquiring fed auth token
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1021,4 +1021,34 @@ public interface ISQLServerDataSource extends javax.sql.CommonDataSource {
*/
void setSendTemporalDataTypesAsStringForBulkCopy(boolean sendTemporalDataTypesAsStringForBulkCopy);

/**
* Returns the value for the connection property 'AADSecurePrincipalId'.
*
* @return 'AADSecurePrincipalId' property value.
*/
String getAADSecurePrincipalId();

/**
* Sets the 'AADSecurePrincipalId' connection property used for Active Directory Service Principal authentication.
*
* @param AADSecurePrincipalId
* Active Directory Service Principal Id.
*/
void setAADSecurePrincipalId(String AADSecurePrincipalId);

/**
* Returns the value for the connection property 'AADSecurePrincipalSecret'.
*
* @return 'AADSecurePrincipalSecret' property value.
*/
String getAADSecurePrincipalSecret();

/**
* Sets the 'AADSecurePrincipalSecret' connection property used for Active Directory Service Principal
* authentication.
*
* @param AADSecurePrincipalSecret
* Active Directory Service Principal secret.
*/
void setAADSecurePrincipalSecret(String AADSecurePrincipalSecret);
}
77 changes: 64 additions & 13 deletions src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ public class SQLServerConnection implements ISQLServerConnection, java.io.Serial
private String clientCertificate = null;
private String clientKey = null;
private String clientKeyPassword = "";
private String aadPrincipalID = "";
private String aadPrincipalSecret = "";

private boolean sendTemporalDataTypesAsStringForBulkCopy = true;

Expand Down Expand Up @@ -404,6 +406,9 @@ class FederatedAuthenticationFeatureExtensionData implements Serializable {
case "ACTIVEDIRECTORYMSI":
this.authentication = SqlAuthentication.ActiveDirectoryMSI;
break;
case "ACTIVEDIRECTORYSERVICEPRINCIPAL":
this.authentication = SqlAuthentication.ActiveDirectoryServicePrincipal;
break;
default:
assert (false);
MessageFormat form = new MessageFormat(
Expand Down Expand Up @@ -1760,6 +1765,22 @@ Connection connectInternal(Properties propsIn,
}
}

sPropKey = SQLServerDriverStringProperty.AAD_SECURE_PRINCIPAL_ID.toString();
sPropValue = activeConnectionProperties.getProperty(sPropKey);
if (null == sPropValue) {
sPropValue = SQLServerDriverStringProperty.AAD_SECURE_PRINCIPAL_ID.getDefaultValue();
activeConnectionProperties.setProperty(sPropKey, sPropValue);
}
aadPrincipalID = sPropValue;

sPropKey = SQLServerDriverStringProperty.AAD_SECURE_PRINCIPAL_SECRET.toString();
sPropValue = activeConnectionProperties.getProperty(sPropKey);
if (null == sPropValue) {
sPropValue = SQLServerDriverStringProperty.AAD_SECURE_PRINCIPAL_SECRET.getDefaultValue();
activeConnectionProperties.setProperty(sPropKey, sPropValue);
}
aadPrincipalSecret = sPropValue;

// Must be set after STATEMENT_POOLING_CACHE_SIZE
sPropKey = SQLServerDriverBooleanProperty.DISABLE_STATEMENT_POOLING.toString();
sPropValue = activeConnectionProperties.getProperty(sPropKey);
Expand Down Expand Up @@ -1866,6 +1887,20 @@ Connection connectInternal(Properties propsIn,
null);
}

if (authenticationString.equalsIgnoreCase(SqlAuthentication.ActiveDirectoryServicePrincipal.toString())
&& ((activeConnectionProperties
.getProperty(SQLServerDriverStringProperty.AAD_SECURE_PRINCIPAL_ID.toString()).isEmpty())
|| (activeConnectionProperties
.getProperty(SQLServerDriverStringProperty.AAD_SECURE_PRINCIPAL_SECRET.toString())
.isEmpty()))) {
if (connectionlogger.isLoggable(Level.SEVERE)) {
connectionlogger.severe(toString() + " "
+ SQLServerException.getErrString("R_NoUserPasswordForActiveServicePrincipal"));
}
throw new SQLServerException(
SQLServerException.getErrString("R_NoUserPasswordForActiveServicePrincipal"), null);
}

if (authenticationString.equalsIgnoreCase(SqlAuthentication.SqlPassword.toString())
&& ((activeConnectionProperties.getProperty(SQLServerDriverStringProperty.USER.toString())
.isEmpty())
Expand Down Expand Up @@ -2715,6 +2750,8 @@ private InetSocketAddress connectHelper(ServerPortPlaceHolder serverInfo, int ti

// We have successfully connected, now do the login. logon takes seconds timeout
executeCommand(new LogonCommand());
aadPrincipalSecret = "";
activeConnectionProperties.remove(SQLServerDriverStringProperty.AAD_SECURE_PRINCIPAL_SECRET.toString());
return inetSocketAddress;
}

Expand Down Expand Up @@ -3872,6 +3909,9 @@ int writeFedAuthFeatureRequest(boolean write, /* if false just calculates the le
case ActiveDirectoryMSI:
workflow = TDS.ADALWORKFLOW_ACTIVEDIRECTORYMSI;
break;
case ActiveDirectoryServicePrincipal:
workflow = TDS.ADALWORKFLOW_ACTIVEDIRECTORYSERVICEPRINCIPAL;
break;
default:
assert (false); // Unrecognized Authentication type for fedauth ADAL request
break;
Expand Down Expand Up @@ -3966,15 +4006,17 @@ private void logon(LogonCommand command) throws SQLServerException {
ntlmPasswordHash, hostName);
}
}

// If the workflow being used is Active Directory Password or Active Directory Integrated and server's prelogin
// response
// for FEDAUTHREQUIRED option indicates Federated Authentication is required, we have to insert FedAuth Feature
// Extension
// in Login7, indicating the intent to use Active Directory Authentication Library for SQL Server.
/*
* If the workflow being used is Active Directory Password or Active Directory Integrated and server's prelogin
* response for FEDAUTHREQUIRED option indicates Federated Authentication is required, we have to insert FedAuth
* Feature Extension in Login7, indicating the intent to use Active Directory Authentication Library for SQL
* Server.
*/
if (authenticationString.equalsIgnoreCase(SqlAuthentication.ActiveDirectoryPassword.toString())
|| ((authenticationString.equalsIgnoreCase(SqlAuthentication.ActiveDirectoryIntegrated.toString())
|| authenticationString.equalsIgnoreCase(SqlAuthentication.ActiveDirectoryMSI.toString()))
|| authenticationString.equalsIgnoreCase(SqlAuthentication.ActiveDirectoryMSI.toString())
|| authenticationString
.equalsIgnoreCase(SqlAuthentication.ActiveDirectoryServicePrincipal.toString()))
&& fedAuthRequiredPreLoginResponse)) {
federatedAuthenticationInfoRequested = true;
fedAuthFeatureExtensionData = new FederatedAuthenticationFeatureExtensionData(TDS.TDS_FEDAUTH_LIBRARY_ADAL,
Expand All @@ -3984,16 +4026,18 @@ private void logon(LogonCommand command) throws SQLServerException {
if (null != accessTokenInByte) {
fedAuthFeatureExtensionData = new FederatedAuthenticationFeatureExtensionData(
TDS.TDS_FEDAUTH_LIBRARY_SECURITYTOKEN, fedAuthRequiredPreLoginResponse, accessTokenInByte);
// No need any further info from the server for token based authentication. So set
// _federatedAuthenticationRequested to true
/*
* No need any further info from the server for token based authentication. So set
* _federatedAuthenticationRequested to true
*/
federatedAuthenticationRequested = true;
}
try {
sendLogon(command, authentication, fedAuthFeatureExtensionData);

// If we got routed in the current attempt,
// the server closes the connection. So, we should not
// be sending anymore commands to the server in that case.
/*
* If we got routed in the current attempt, the server closes the connection. So, we should not be sending
* anymore commands to the server in that case.
*/
if (!isRoutedInCurrentAttempt) {
originalCatalog = sCatalog;
String sqlStmt = sqlStatementToInitialize();
Expand Down Expand Up @@ -4453,6 +4497,13 @@ private SqlFedAuthToken getFedAuthToken(SqlFedAuthInfo fedAuthInfo) throws SQLSe
fedAuthToken = SQLServerSecurityUtility.getMSIAuthToken(fedAuthInfo.spn,
activeConnectionProperties.getProperty(SQLServerDriverStringProperty.MSI_CLIENT_ID.toString()));

// Break out of the retry loop in successful case.
break;
} else if (authenticationString
.equalsIgnoreCase(SqlAuthentication.ActiveDirectoryServicePrincipal.toString())) {
fedAuthToken = SQLServerMSAL4JUtils.getSqlFedAuthTokenPrincipal(fedAuthInfo, aadPrincipalID,
aadPrincipalSecret, authenticationString);

// Break out of the retry loop in successful case.
break;
} else if (authenticationString.equalsIgnoreCase(SqlAuthentication.ActiveDirectoryIntegrated.toString())) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1018,6 +1018,30 @@ public void setClientKeyPassword(String password) {
setStringProperty(connectionProps, SQLServerDriverStringProperty.CLIENT_KEY_PASSWORD.toString(), password);
}

@Override
public String getAADSecurePrincipalId() {
return getStringProperty(connectionProps, SQLServerDriverStringProperty.AAD_SECURE_PRINCIPAL_ID.toString(),
SQLServerDriverStringProperty.AAD_SECURE_PRINCIPAL_ID.getDefaultValue());
}

@Override
public void setAADSecurePrincipalId(String AADSecurePrincipalId) {
setStringProperty(connectionProps, SQLServerDriverStringProperty.AAD_SECURE_PRINCIPAL_ID.toString(),
AADSecurePrincipalId);
}

@Override
public String getAADSecurePrincipalSecret() {
return getStringProperty(connectionProps, SQLServerDriverStringProperty.AAD_SECURE_PRINCIPAL_SECRET.toString(),
SQLServerDriverStringProperty.AAD_SECURE_PRINCIPAL_SECRET.getDefaultValue());
}

@Override
public void setAADSecurePrincipalSecret(String AADSecurePrincipalSecret) {
setStringProperty(connectionProps, SQLServerDriverStringProperty.AAD_SECURE_PRINCIPAL_SECRET.toString(),
AADSecurePrincipalSecret);
}

@Override
public boolean getSendTemporalDataTypesAsStringForBulkCopy() {
return getBooleanProperty(connectionProps,
Expand Down
20 changes: 16 additions & 4 deletions src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ enum SqlAuthentication {
SqlPassword,
ActiveDirectoryPassword,
ActiveDirectoryIntegrated,
ActiveDirectoryMSI;
ActiveDirectoryMSI,
ActiveDirectoryServicePrincipal;

static SqlAuthentication valueOfString(String value) throws SQLServerException {
SqlAuthentication method = null;
Expand All @@ -82,6 +83,9 @@ static SqlAuthentication valueOfString(String value) throws SQLServerException {
method = SqlAuthentication.ActiveDirectoryIntegrated;
} else if (value.toLowerCase(Locale.US).equalsIgnoreCase(SqlAuthentication.ActiveDirectoryMSI.toString())) {
method = SqlAuthentication.ActiveDirectoryMSI;
} else if (value.toLowerCase(Locale.US)
.equalsIgnoreCase(SqlAuthentication.ActiveDirectoryServicePrincipal.toString())) {
method = SqlAuthentication.ActiveDirectoryServicePrincipal;
} else {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_InvalidConnectionSetting"));
Object[] msgArgs = {"authentication", value};
Expand Down Expand Up @@ -366,7 +370,9 @@ enum SQLServerDriverStringProperty {
KEY_STORE_PRINCIPAL_ID("keyStorePrincipalId", ""),
CLIENT_CERTIFICATE("clientCertificate", ""),
CLIENT_KEY("clientKey", ""),
CLIENT_KEY_PASSWORD("clientKeyPassword", "");
CLIENT_KEY_PASSWORD("clientKeyPassword", ""),
AAD_SECURE_PRINCIPAL_ID("AADSecurePrincipalId", ""),
AAD_SECURE_PRINCIPAL_SECRET("AADSecurePrincipalSecret", "");

private final String name;
private final String defaultValue;
Expand Down Expand Up @@ -582,7 +588,9 @@ public final class SQLServerDriver implements java.sql.Driver {
new String[] {SqlAuthentication.NotSpecified.toString(), SqlAuthentication.SqlPassword.toString(),
SqlAuthentication.ActiveDirectoryPassword.toString(),
SqlAuthentication.ActiveDirectoryIntegrated.toString(),
SqlAuthentication.ActiveDirectoryMSI.toString()}),
SqlAuthentication.ActiveDirectoryMSI.toString(),
SqlAuthentication.ActiveDirectoryServicePrincipal
.toString()}),
new SQLServerDriverPropertyInfo(SQLServerDriverIntProperty.SOCKET_TIMEOUT.toString(),
Integer.toString(SQLServerDriverIntProperty.SOCKET_TIMEOUT.getDefaultValue()), false, null),
new SQLServerDriverPropertyInfo(SQLServerDriverBooleanProperty.FIPS.toString(),
Expand Down Expand Up @@ -634,7 +642,11 @@ public final class SQLServerDriver implements java.sql.Driver {
SQLServerDriverBooleanProperty.SEND_TEMPORAL_DATATYPES_AS_STRING_FOR_BULK_COPY.toString(),
Boolean.toString(SQLServerDriverBooleanProperty.SEND_TEMPORAL_DATATYPES_AS_STRING_FOR_BULK_COPY
.getDefaultValue()),
false, TRUE_FALSE),};
false, TRUE_FALSE),
new SQLServerDriverPropertyInfo(SQLServerDriverStringProperty.AAD_SECURE_PRINCIPAL_ID.toString(),
SQLServerDriverStringProperty.AAD_SECURE_PRINCIPAL_ID.getDefaultValue(), false, null),
new SQLServerDriverPropertyInfo(SQLServerDriverStringProperty.AAD_SECURE_PRINCIPAL_SECRET.toString(),
SQLServerDriverStringProperty.AAD_SECURE_PRINCIPAL_SECRET.getDefaultValue(), false, null)};

/**
* Properties that can only be set by using Properties. Cannot set in connection string
Expand Down
Loading

0 comments on commit 9f7b5c6

Please sign in to comment.