การเชื่อมต่อกับ Azure SQL โดยใช้ Service Principal ใน NodeJS แต่โทเค็นถูกปฏิเสธ

Aug 16 2020

ฉันมีปัญหาในการรับแอปพลิเคชัน NodeJS เพื่อเชื่อมต่อกับฐานข้อมูล Azure SQL โดยใช้ Service Principal อย่างไรก็ตามเมื่อฉันพยายามทำสิ่งเดียวกันกับ C # Snippet มันก็ใช้ได้ดี สิ่งที่ฉันสังเกตเห็นคือโทเค็นที่ส่งคืนโดย auth ในทั้งสองภาษานั้นแตกต่างกันเล็กน้อยและถ้าฉันใช้โทเค็นที่ถูกต้องจาก C # และฮาร์ดโค้ดลงใน NodeJS ตอนนี้การเชื่อมต่อ SQL ของฉันจะสำเร็จ

ฉันใช้ ms-rest-azure เป็นครั้งแรกในการตรวจสอบสิทธิ์ของฉันและระบุ clientId, tenantId และ clientSecret สิ่งนี้จะคืนกลับข้อมูลรับรองที่ถูกต้องซึ่งฉันกำลังแยก accessToken

จากนั้นฉันใช้ความน่าเบื่อในการพยายามเชื่อมต่อกับ Azure SQL ที่ * .database.windows.net และให้ค่า accessToken ในการกำหนดค่า

ฉันเพิ่งเข้าสู่ระบบล้มเหลวสำหรับผู้ใช้ '<โทเค็นระบุหลัก>'

ฉันทำอะไรผิดในการเข้าสู่ระบบ ms-rest-azure เพื่อให้โทเค็นที่ Azure SQL ปฏิเสธ สิ่งหนึ่งที่ผมเห็นก็คือว่าโทเค็นการทำงานที่มีผู้ชมdatabase.windows.netที่-เป็นหนึ่งจาก MS-ส่วนที่เหลือเป็นสีฟ้าmanagement.core.windows.net

ฉันติดอยู่สองสามวันถ้าใครมีเบาะแสที่นี่จะดีมาก เอกสารประกอบเกี่ยวกับ ms-rest-azure ดูเหมือนจะไม่มีอยู่จริงและเพียงแค่ให้การรันบนหน้า Azure Sales แก่คุณ

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);
        }
    });   
})

คำตอบ

2 JimXu Aug 18 2020 at 02:34

เมื่อเราใช้ใบรับรองในแพ็คเกจms-rest-azureเพื่อรับโทเค็นโดยค่าเริ่มต้นผู้ชมของโทเค็นคือhttps://management.core.windows.net/มันสามารถใช้เพื่อเรียก Azure rest api ได้ ถ้าเราต้องการที่จะใช้ Azure โฆษณา token เชื่อมต่อกับ SQL https://database.windows.net/ผู้ชมของโทเค็นที่ควรจะเป็น ดังนั้นเราควรอัปเดตรหัสที่ใช้รับโทเค็นเป็น

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

ตัวอย่างเช่น

  1. สร้างหลักบริการ
az login
az ad sp create-for-rbac -n 'MyApp' --skip-assignment
  1. กำหนดค่าฐานข้อมูล SQL

ก. ใช้ผู้ดูแลระบบ Azure Sql AD ของคุณเพื่อเชื่อมต่อ Azure SQL กับ SSMS

ข. เพิ่มหลักบริการในฐานข้อมูลที่คุณต้องการใช้

create user [<Azure_AD_principal_name>] from external provider
ALTER ROLE db_owner ADD MEMBER [<Azure_AD_principal_name>]
     
  1. รหัส
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);
  });

สำหรับรายละเอียดเพิ่มเติมโปรดดูที่นี่