Łączenie się z Azure SQL przy użyciu nazwy głównej usługi w NodeJS, ale token jest odrzucany

Aug 16 2020

Mam problem z uzyskaniem połączenia mojej aplikacji NodeJS z Azure SQL Database przy użyciu nazwy głównej usługi. Jednak kiedy próbuję zrobić to samo z fragmentem C #, działa dobrze. Zauważyłem, że tokeny zwracane przez auth w obu językach są nieco inne i jeśli wezmę poprawny token z C # i zakoduję go na stałe w NodeJS, moje połączenie SQL kończy się pomyślnie.

Najpierw używam ms-rest-azure do przeprowadzania uwierzytelniania i dostarczania mojego clientId, tenantId i clientSecret. To zwraca prawidłowe poświadczenie, z którego wyodrębniam accessToken.

Następnie używam żmudnego połączenia się z Azure SQL pod adresem * .database.windows.net i podając wartość accessToken w konfiguracji.

Po prostu otrzymuję błąd logowania dla użytkownika „<nazwa główna z tokenem>”

Co robię źle w logowaniu do usługi ms-rest-azure, aby uzyskać token odrzucony przez usługę Azure SQL? Jedną rzeczą, jaką zauważyłem, jest to, że działający token ma grupę docelową database.windows.net , gdzie - tak jak ten z ms-rest-azure - to management.core.windows.net .

Utknąłem na kilka dni, jeśli ktoś ma tu jakieś wskazówki, byłoby super. Dokumentacja dotycząca ms-rest-azure wydaje się prawie nie istnieć i po prostu zapewnia obejście stron sprzedaży platformy Azure.

const msRestAzure = require('ms-rest-azure');
const { reject } = require('async');


let clientSecret = "xxx";
let serverName = "xxx.database.windows.net";
let databaseName = "xxx";
let clientId = "xxx";
let tenantId = "xxx";

azureCredentials = msRestAzure.loginWithServicePrincipalSecret(clientId, clientSecret, tenantId, function(err, credentials) {
    if (err) return console.log(err);
    credentials.getToken((err, results) => {
        if(err) return reject(err);
        
        let accessToken = results.accessToken;

        var Connection = require('tedious').Connection;
        var Request = require('tedious').Request;

        var config = {
            server: serverName,
            authentication: {
                type: 'azure-active-directory-access-token',
                options: {
                    token: accessToken
                }
            }
            ,options: {
                debug: {
                packet: true,
                data: true,
                payload: true,
                token: false,
                log: true
                },
                database: databaseName,
                encrypt: true
            }  
        };

        var connection = new Connection(config);

        connection.connect();

        connection.on('connect', function(err) {
            if(err) {
                console.log(err);
            }
            executeStatement();
        }
        );

        connection.on('debug', function(text) {
            console.log(text);
        }
        );

        function executeStatement() {
        request = new Request("select * from Text", function(err, rowCount) {
            if (err) {
            console.log(err);
            } else {
            console.log(rowCount + ' rows');
            }

            connection.close();
        });

        request.on('row', function(columns) {
            columns.forEach(function(column) {
            if (column.value === null) {
                console.log('NULL');
            } else {
                console.log(column.value);
            }
            });
        });

        request.on('done', function(rowCount, more) {
            console.log(rowCount + ' rows returned');
        });

        connection.execSql(request);
        }
    });   
})

Odpowiedzi

2 JimXu Aug 18 2020 at 02:34

kiedy używamy certyfikatów w pakiecie, ms-rest-azureaby uzyskać token, domyślnie odbiorcą tokenu jest https://management.core.windows.net/, po prostu można go użyć do wywołania Azure rest api. Jeśli chcemy użyć tokenu usługi Azure AD do połączenia sql, odbiorcami tokenu powinny być https://database.windows.net/. Dlatego powinniśmy zaktualizować kod używany do uzyskania tokena jako

msrestAzure.loginWithServicePrincipalSecret(
    clientId,
    clientSecret,
    tenantId,
    {
      tokenAudience: "https://database.windows.net/",
    },

Na przykład

  1. Utwórz jednostkę usługi
az login
az ad sp create-for-rbac -n 'MyApp' --skip-assignment
  1. Skonfiguruj bazę danych SQL

za. Użyj administratora usługi Azure Sql AD, aby połączyć Azure SQL vai SSMS

b. Dodaj jednostkę usługi do bazy danych, której chcesz użyć

create user [<Azure_AD_principal_name>] from external provider
ALTER ROLE db_owner ADD MEMBER [<Azure_AD_principal_name>]
     
  1. kod
var msrestAzure = require("ms-rest-azure");
var { Connection, Request } = require("tedious");

let clientSecret = "xxx";
let serverName = "xxx.database.windows.net";
let databaseName = "xxx";
let clientId = "xxx";
let tenantId = "xxx";

async function getConnect() {
  // way for Azure Service Principal
  let databaseCredentials = await msrestAzure.loginWithServicePrincipalSecret(
    clientId,
    clientSecret,
    tenantId,
    {
      tokenAudience: "https://database.windows.net/",
    },
  );

  // getting access token
  let databaseAccessToken = await new Promise((resolve, reject) => {
    databaseCredentials.getToken((err, results) => {
      if (err) return reject(err);
      resolve(results.accessToken);
    });
  });
  var config = {
    server: serverName,
    authentication: {
      type: "azure-active-directory-access-token",
      options: {
        token: databaseAccessToken,
      },
    },
    options: {
      debug: {
        packet: true,
        data: true,
        payload: true,
        token: false,
        log: true,
      },
      database: databaseName,
      encrypt: true,
    },
  };

  var connection = new Connection(config);
  connection.connect();
  connection.on("connect", function (err) {
    if (err) {
      console.log(err);
    }
    executeStatement(connection);
  });

  connection.on("debug", function (text) {
    console.log(text);
  });
}
function executeStatement(connection) {
  request = new Request("select * from CSVTest", function (err, rowCount) {
    if (err) {
      console.log(err);
    } else {
      console.log(rowCount + " rows");
    }

    connection.close();
  });

  request.on("row", function (columns) {
    columns.forEach(function (column) {
      if (column.value === null) {
        console.log("NULL");
      } else {
        console.log(column.value);
      }
    });
  });

  request.on("done", function (rowCount, more) {
    console.log(rowCount + " rows returned");
  });

  connection.execSql(request);
}

getConnect()
  .then(() => {
    console.log("run successfully");
  })
  .catch((err) => {
    console.log(err);
  });

Więcej szczegółów można znaleźć tutaj