练习 - 实现多租户数据
本练习将带你完成以下过程:
- 在服务器部署模型中安装 Azure Database for PostgreSQL。
- 使用示例库存数据创建数据库。
- 将服务器与 Microsoft Entra ID 集成。
- 实现一个基于 Node.js 的简单应用程序,该应用程序依靠 Microsoft Entra 身份验证来查询数据库。
注意
本练习演示了一种简化的方法,该方法通过模拟 Azure Database for PostgreSQL 超大规模部署模型的多租户功能来为多个租户提供支持。 它还提供一种依靠 Microsoft Entra B2B 功能在多租户方案中实现 Microsoft Entra 身份验证的简化方法。 Microsoft Entra ID 本身就支持多租户应用程序,但对它们的详细介绍超出了本模块的范围。
通过学习本练习,你将能够:
- 创建 Azure Database for PostgreSQL 服务器的实例。
- 连接到 Azure Database for PostgreSQL 服务器。
- 创建数据库和示例表。
- 将 Azure Database for PostgreSQL 服务器与 Microsoft Entra ID 集成。
- 使用 Microsoft Entra ID 注册应用程序。
- 实现一个 Microsoft Entra 集成的基于 Node.js 的简单应用程序。
- 验证基于 Node.js 的应用程序的功能。
先决条件
若要进行此练习,你需要:
- Azure 订阅。
- Microsoft 帐户或 Microsoft Entra 帐户,该帐户在 Azure 订阅关联的 Microsoft Entra 租户中具有全局管理员角色并在 Azure 订阅中具有所有者或参与者角色。
- 已经完成本模块的第一个练习。
创建 Azure Database for PostgreSQL 服务器实例
首先创建一个 Azure Database for PostgreSQL 服务器实例:
如果需要,请启动 Web 浏览器,导航到 Azure 门户并登录,以访问要在本模块中使用的 Azure 订阅。
使用 Azure 门户页面开头的“搜索资源、服务和文档”文本框搜索“Azure Database for PostgreSQL”,然后在结果列表中的“服务”部分中选择“Azure Database for PostgreSQL”。
在“Azure Database for PostgreSQL 服务器”页上选择“+ 创建”。
在“选择 Azure Database for PostgreSQL 部署选项”边栏选项卡上的“Azure Database for PostgreSQL”图块上,为“资源类型”选择“单一服务器”。
选择创建。
在“单一服务器”边栏选项卡的“基本信息”选项卡上,配置以下设置,然后选择“查看 + 创建”,同时将所有其他设置保留为默认值:
设置 配置 订阅 选择要在本模块中使用的 Azure 订阅的名称。 资源组 创建名为 postgresql-db-RG 的新资源组。 服务器名称 输入由小写字母、数字或破折号组成且以字母开头的唯一名称。 数据源 选择“无”。 位置 选择离实验室环境位置最近的 Azure 区域,你可以在其中创建 Azure Database for PostgreSQL 实例。 版本 选择“11”。 计算 + 存储 选择“配置服务器”链接。 在“配置”边栏选项卡上,选择“基本信息”,将“vCore”值设置为 1,将“存储”设置为 5 GB,然后选择“确定”。 管理员用户名 输入 student。 密码 输入 <password>
在“单一服务器”边栏选项卡的“查看 + 创建”选项卡上,选择“创建”。
等待预配完成。 这可能需要大约五分钟。
注意
预配过程会自动在目标服务器中创建一个名为 postgres 的数据库。
连接到 Azure Database for PostgreSQL 服务器
预配 Azure Database for PostgreSQL 服务器后,你将使用 psql 工具连接到该服务器。
I在“Microsoft.PostgreSQLServer.createPostgreSqlServer 概览”边栏选项卡上,选择“转到资源”。
在“部署”边栏选项卡上,在垂直菜单的“设置”部分中选择“连接安全性”。
在“连接安全性”边栏选项卡上,将“允许访问 Azure 服务”设置为“是”,选择“+ 添加客户端 IP”,将“强制执行 SSL 连接”设置为“已禁用”,然后选择“保存”。
注意
这些设置将允许从计算机和 Azure 中运行的应用程序连接到数据库。
备注
禁用 SSL 强制执行是为了简化后续练习。 通常,你应该保持启用此设置。
在显示 Azure 门户中的“部署”边栏选项卡的浏览器窗口中,在垂直菜单中选择“概述”。
在“概要”部分中,找到“服务器名称”和“管理员用户名”标签旁边的条目并记录它们的值。
备注
请注意,用户名包含 @ 符号,后跟上一个任务中指定的服务器名称。
在显示 Azure 门户中的“Azure Database for PostgreSQL 单一服务器”边栏选项卡的浏览器窗口中,在垂直菜单的“设置”部分中选择“连接字符串”。
在连接字符串列表中,将 psql 连接字符串的值复制到剪贴板并记录下来,以便稍后在本练习中使用。
注意
该连接字符串采用以下语法,其中,
<server_name>
占位符表示之前在此任务中标识的服务器名称:psql "host=<server_name>.postgres.database.azure.com port=5432 dbname={your_database} user=student@<server_name> password={your_password} sslmode=require"
在 Azure 门户中,通过选择搜索文本框旁边工具栏中的 Cloud Shell 图标来打开 Cloud Shell 的 Bash 会话。
在 Cloud Shell 窗格上的 Bash 会话中,粘贴剪贴板中 psql 连接字符串的值,并进行修改,使其与以下命令匹配,然后运行它以连接到新部署的 Azure Database for PostgreSQL 服务器实例上托管的 postgres 数据库。
<server_name>
占位符的值已包含在从剪贴板粘贴的连接字符串中:psql "host=<server_name>.postgres.database.azure.com port=5432 dbname=postgres user=student@<server_name>.postgres.database.azure.com password=<enter your password> sslmode=require"
备注
成功连接后,你将看到
postgres=>
提示符。
创建数据库和示例表
在 Cloud Shell 窗格中,在
postgres=>
提示符下运行以下命令,以新建一个名为 cnamtinventory 数据库:CREATE DATABASE cnamtinventory;
运行以下命令,将连接切换到新创建的数据库:
\c cnamtinventory
运行以下命令以创建 tenants 表:
CREATE TABLE tenants ( id bigserial PRIMARY KEY, name text NOT NULL, created_at TIMESTAMP DEFAULT NOW()::date, updated_at TIMESTAMP DEFAULT NOW()::date );
运行以下命令以创建 inventory 表:
CREATE TABLE inventory ( id bigserial, tenant_id bigint REFERENCES tenants (id), name VARCHAR(50), quantity INTEGER, date DATE NOT NULL DEFAULT NOW()::date, created_at TIMESTAMP DEFAULT NOW()::date, updated_at TIMESTAMP DEFAULT NOW()::date, PRIMARY KEY (tenant_id, id, date) ) PARTITION BY RANGE (date); CREATE TABLE inventory_default PARTITION OF inventory DEFAULT;
备注
数据根据日期列的值进行分区。
运行以下命令,验证表是否创建成功:
\dt
运行以下命令,将示例数据加载到 tenants 表中:
INSERT INTO tenants (id, name) VALUES (1, 'adatum'); INSERT INTO tenants (id, name) VALUES (2, 'contoso');
运行以下命令,将示例数据加载到 inventory 表中:
INSERT INTO inventory (id, tenant_id, name, quantity) VALUES (1, 1, 'yogurt', 200); INSERT INTO inventory (id, tenant_id, name, quantity) VALUES (2, 1, 'milk', 100); INSERT INTO inventory (id, tenant_id, name, quantity) VALUES (1, 2, 'yogurt', 20); INSERT INTO inventory (id, tenant_id, name, quantity) VALUES (2, 2, 'milk', 10);
运行以下命令,验证 inventory 表是否包含插入的数据:
SELECT * FROM inventory;
关闭 Cloud Shell 窗格。
将 Azure Database for PostgreSQL 服务器与 Microsoft Entra ID 集成
若要将 Azure Database for PostgreSQL 服务器实例与 Microsoft Entra ID 集成,必须提供 Microsoft Entra 用户帐户作为服务器的指定 Active Directory 管理员。 为此,你将使用上一个任务中创建的 adatumadmin1 用户帐户。 你需要使用该用户帐户登录到服务器。 届时,你将能够创建基于 Microsoft Entra ID 的数据库用户并为其分配数据库角色。 你将使用上一个练习中创建的 adatumuser1、adatumgroup1 和 contosouser1 Microsoft Entra 对象。
在显示 Azure 门户中的“Azure Database for PostgreSQL 服务器”边栏选项卡的浏览器窗口中,在垂直菜单的“设置”部分中选择“Active Directory 管理员”,然后在工具栏中,选择“设置管理员”。
在“Active Directory 管理员”窗格的 Microsoft Entra 用户帐户列表中,选择上一个练习中创建的 adatumadmin1 用户帐户,选择“选择”,然后选择“保存”。
在 Incognito/InPrivate 模式下打开另一个 Web 浏览器窗口,导航到 Azure 门户,然后使用上一个练习中创建的 adatumadmin1 用户帐户登录。
在 Azure 门户中,通过选择搜索文本框旁边工具栏中的 Cloud Shell 图标来打开 Cloud Shell。
当系统提示你在 Bash 和 PowerShell 之间二选一时,请选择 Bash,然后在出现“未装载任何存储”消息时,选择“创建存储”。
在 Cloud Shell 窗格上的 Bash 会话中,运行以下命令,检索和显示访问 Azure Database for PostgreSQL 所需的 Microsoft Entra 访问令牌:
FULL_TOKEN=$(az account get-access-token --resource-type oss-rdbms) echo $FULL_TOKEN
注意
该命令会生成包含 Base 64 编码令牌的输出,该令牌用于向 Azure Database for PostgreSQL 资源标识经过身份验证的用户。
输出格式如下:
{ "accessToken": "<placeholder for token value>", "expiresOn": "2021-05-21 18:22:44.000000", "subscription": "cccc2c2c-dd3d-ee4e-ff5f-aaaaaa6a6a6a", "tenant": "eeeeffff-4444-aaaa-5555-bbbb6666cccc", "tokenType": "Bearer" }
运行以下命令,将 PGPASSWORD 变量的值设置为上一步中运行的命令的输出中的访问令牌值:
export PGPASSWORD=$(echo $FULL_TOKEN | jq -r '.accessToken')
运行以下命令,以使用 psql 工具和 Microsoft Entra 身份验证(将
<server_name>
占位符替换为之前在本练习中标识的服务器名称)连接到 cnamtinventory 数据库:DOMAIN_NAME=$(az rest --method GET --url 'https://management.azure.com/tenants?api-version=2020-01-01' --query "value[0].defaultDomain" -o tsv) psql "host=<server_name>.postgres.database.azure.com user=adatumadmin1@$DOMAIN_NAME@<server_name> dbname=cnamtinventory sslmode=require"
注意
成功连接后,你应该会看到
cnamtinventory=>
提示符。在
cnamtinventory=>
提示符下,运行以下命令,以创建与上一个练习中创建的 adatumgroup1 Microsoft Entra 组对应的数据库角色:CREATE ROLE "adatumgroup1" WITH LOGIN IN ROLE azure_ad_user;
运行以下命令,验证是否已成功创建角色:
SELECT rolname FROM pg_roles;
运行以下命令,将 inventory 表的 SELECT 权限授予上一个练习中创建的 adatumgroup1:
GRANT SELECT ON inventory TO adatumgroup1;
以 adatumadmin1 用户帐户的身份注销,并关闭 Incognito/InPrivate 模式 Web 浏览器窗口。
使用 Microsoft Entra ID 注册应用程序
若要实现使用 Microsoft Entra 身份验证访问 Azure Database for PostgreSQL 数据库的基于 Node.js 的示例应用程序,必须创建 Microsoft Entra 应用程序对象和相应的安全主体。 这将允许基于 Node.js 的应用程序在访问数据库对象时模拟 Microsoft Entra 用户。
在 Azure 门户中,使用“搜索资源、服务和文档”文本框搜索“Microsoft Entra ID”,然后在结果列表中,选择“Microsoft Entra ID”。
在 Microsoft Entra 窗格上,在垂直菜单的“管理”部分中选择“应用注册”。
在“应用注册”边栏选项卡上,选择“+ 新建注册”。
在“注册应用程序”边栏选项卡上的“名称”文本框中,输入 cna-app。 在“支持的帐户类型”部分中,确保选中“仅此组织目录中的帐户 (仅默认目录 - 单一租户)”选项。 在“重定向 URI (可选)”部分中,将“Web”条目设置为
http://localhost:8080/redirect
,然后选择“注册”。注意
可以选择为 Microsoft Entra 注册应用程序配置多租户支持。 但是,对这种方法的详细介绍超出了本模块的范围。
注意
部署应用程序后,需要修改“重定向 URI (可选)”值以反映其实际 URL。
在 cna-app 边栏选项卡上,查看生成的设置,并记录“应用程序(客户端) ID”和“目录(租户) ID”属性的值。
在 cna-app 边栏选项卡的“管理”部分中,选择“证书和机密”,然后选择“+ 新建客户端密码”。
在“添加客户端密码”边栏选项卡的“说明”文本框中,输入 cna-secret-0。 保留“到期”下拉列表条目的默认值并选择“添加”。
返回“cna-app | 证书和机密”边栏选项卡,复制新生成的机密的值。
注意
在离开此边栏选项卡之前,请务必复制机密值,因为离开后,就再也无法检索该值。 如果出现这种情况,请再创建一个机密。
在“cna-app | 证书和机密”边栏选项卡上,在垂直菜单的“管理”部分,选择“API 权限”。
在“cna-app | API 权限”边栏选项卡上,选择“+ 添加权限”,在“请求 API 权限”边栏选项卡上,选择“我的组织使用的 API”选项卡,在搜索文本框中输入“Azure OSSRDBMS 数据库”,然后在结果列表中选择“Azure OSSRDBMS 数据库”。
在“请求 API 权限”边栏选项卡上,选择“委托的权限”,选中“user_impersonation”复选框,然后选择“添加权限”。
返回“cna-app | API 权限”边栏选项卡,选择“为默认目录授予管理员许可”,并在提示确认时选择“是”。
在“cna-app | API 权限”边栏选项卡上,验证是否已授予权限。
实现一个 Microsoft Entra 集成的基于 Node.js 的简单应用程序
在 Microsoft Entra 租户中注册应用程序后,现在可以继续执行其实现。
在 Azure 门户中,通过选择搜索文本框旁边工具栏中的 Cloud Shell 图标来启动 Cloud Shell 中的 Bash 会话。
在 Cloud Shell 窗格上的 Bash 会话中运行以下命令,以在新目录中初始化 Node.js 项目:
mkdir -p cna-aadexpress && cd cna-aadexpress npm init -y
运行以下命令,将所需的包添加到项目的依赖项中:
npm install express npm install pg npm install @azure/msal-node
运行以下命令,在项目的根目录中创建一个名为 index.js 的文件:
touch ./index.js
使用 nano 编辑器打开文件 index.js 并添加以下内容。 你将在本单元的后面部分创建一个应用名称,以替换占位符
<webapp_name>
。 用在本练习前面记录的实际值替换占位符<client_id>
、<tenant_id>
、<client_secret>
和<server_name>
(不包括后缀.postgres.database.azure.com
):注意
占位符
<client_id>
和<tenant_id>
对应于本练习前面引用的“应用程序(客户端) ID”和“目录(租户) ID”属性。// Import dependencies const express = require("express"); const msal = require('@azure/msal-node'); const pg = require('pg'); const port = process.env.PORT || 8080 // Initialize express const app = express(); app.use(express.json()); app.listen(port, () => console.log(`Sample app is listening on port ${port}!`)) // Authentication parameters const config = { auth: { clientId: "<client_id>", authority: "https://login.microsoftonline.com/<tenant_id>", clientSecret: "<client_secret>" }, system: { loggerOptions: { loggerCallback(loglevel, message, containsPii) { console.log(message); }, piiLoggingEnabled: false, logLevel: msal.LogLevel.Verbose, } } }; var outputrows = "" // Initialize MSAL Node object using authentication parameters const cca = new msal.ConfidentialClientApplication(config); app.get('/auth', (req, res) => { redirectUri = req.hostname.toLowerCase()=="localhost" ? "http://localhost:8080/redirect" : "https://<webapp_name>.azurewebsites.net/redirect"; // Construct a request object for auth code const authCodeUrlParameters = { scopes: ["https://ossrdbms-aad.database.windows.net/user_impersonation"], redirectUri: redirectUri, }; // Request auth code, then redirect cca.getAuthCodeUrl(authCodeUrlParameters) .then((response) => { res.redirect(response); }).catch((error) => res.send(error)); }); app.get('/redirect', (req, res) => { redirectUri = req.hostname.toLowerCase()=="localhost" ? "http://localhost:8080/redirect" : "https://<webapp_name>.azurewebsites.net/redirect"; // Use the auth code in redirect request to construct a token request object const tokenRequest = { code: req.query.code, scopes: ["https://ossrdbms-aad.database.windows.net/user_impersonation"], redirectUri: redirectUri, }; // Exchange the auth code for tokens cca.acquireTokenByCode(tokenRequest) .then((response) => { //res.send(response); var username = 'adatumgroup1'; var databasename = 'cnamtinventory'; var servername = '<server_name>'; var tablename = 'inventory'; process.env.PGPASSWORD = response.accessToken; const connectionString = `postgres://${username}@${servername}@${servername}.postgres.database.azure.com:5432/${databasename}?ssl=true`; res.write(connectionString + "\n\n"); res.write(response.accessToken + "\n\n"); const client = new pg.Client(connectionString); client.connect(err => { if (err) throw err; else { queryDatabase(response.account.name); } }); function queryDatabase(tenant_id) { console.log(`Running query to PostgreSQL server: ${servername}`); switch (tenant_id) { case "adatumuser1": id = "1"; break; case "contosouser1": id = "2"; break; } const query = `SELECT * FROM ${tablename} WHERE tenant_id = ${id};`; client.query(query) .then(qresponse => { const rows = qresponse.rows; rows.map(row => { var singlerow = `${JSON.stringify(row)}`; console.log(singlerow); outputrows += singlerow + "\n"; }); res.write(outputrows); res.end(); process.exit(); }) .catch(err => { console.log(err); }); } }).catch((error) => res.write(error)); });
注意
多租户 Microsoft Entra 注册应用程序使用通用授权 URL
authority: "https://login.microsoftonline.com/common"
,但针对你的情况,需要使用包含租户 ID 的单租户 URL。注意
请记住,部署应用程序后,需要替换“重定向 URL”的值,以匹配实际的重定向 URL。
使用 nano 编辑器编辑项目根中的 package.json 文件,并将其替换为以下内容:
{ "name": "node-express", "version": "1.0.0", "description": "Node.js express sample", "main": "index.js", "scripts": { "start": "node index.js" }, "author": "", "license": "ISC", "dependencies": { "@azure/msal-node": "^1.1.0", "body-parser": "^1.19.0", "express": "^4.17.1", "http": "0.0.0", "morgan": "^1.10.0", "pg": "^8.6.0" } }
验证基于 Node.js 的应用程序的功能
终于准备好测试 Web 应用的功能了。 虽然可以将其容器化,但为简单起见,需要将其部署到 Azure 应用服务。 这将提供一种快速验证其功能的方法,并确保将其容器化是一个可行的选择。
在 Cloud Shell 窗格上的 Bash 会话中运行以下命令,以创建一个托管 Azure Web 应用的资源组,你将在其中部署 Node.js Express 应用:
RG1NAME=postgresql-db-RG LOCATION=$(az group show --resource-group $RG1NAME --query location --output tsv) RG2NAME=cna-aadexpress-RG az group create --name $RG2NAME --location $LOCATION
运行以下命令,创建托管新 Azure Web 应用的免费层 Azure 应用服务计划:
SPNAME=aadexpress-sp az appservice plan create --name $SPNAME --resource-group $RG2NAME --sku F1 --is-linux
运行以下命令,创建新的基于 Node.js 的 Azure Web 应用:
WEBAPPNAME=aadexpress$RANDOM$RANDOM az webapp create --name $WEBAPPNAME --resource-group $RG2NAME --plan $SPNAME --runtime "NODE|16-lts"
运行以下命令,标识 Web 应用的名称:
echo $WEBAPPNAME
使用 nano 编辑器打开 index.js 文件,用上一步中标识的名称替换两个
<webapp_name>
占位符,保存更改,然后关闭文件。注意
确保替换两个
<webapp_name>
占位符。在显示 Azure 门户的 Web 浏览器窗口中打开另一个选项卡,导航到 Azure 门户并在系统提示时登录,以访问要在本模块中使用的 Azure 订阅。
在 Azure 门户中,使用“搜索资源、服务和文档”文本框搜索“Microsoft Entra ID”,然后在结果列表中,选择“Microsoft Entra ID”。
在 Microsoft Entra 窗格上,导航到“应用注册”窗格,选择“cna-app”条目,在垂直菜单的“管理”部分中,选择“身份验证”。
在“cna-app | 身份验证”边栏选项卡上,修改“重定向 URI”的值,以匹配你在 index.js 文件中更新的条目并保存更改。
切换回显示 Cloud Shell 窗格上的 Bash 会话的 Web 浏览器选项卡,运行以下命令,以初始化本地 Git 存储库并提交 main 分支中的所有更改:
cd ~/cna-aadexpress git config --global user.email "user1@adatum.com" git config --global user.name "Adatum User1" git init git add -A git commit -m "Initial Commit"
运行以下命令,设置用户级部署凭据:
DEPLOYMENTUSER=m06User$RANDOM DEPLOYMENTPASS=m06Pass$RANDOM$RANDOM az webapp deployment user set --user-name $DEPLOYMENTUSER --password $DEPLOYMENTPASS
运行以下命令,标识用户级部署凭据并记录其值,因为稍后将在本任务中用到它们:
echo $DEPLOYMENTUSER echo $DEPLOYMENTPASS
运行以下命令,标识将用作
git push
命令目标的 Azure Web 应用部署 URL:RG2NAME=cna-aadexpress-RG WEBAPPNAME=$(az webapp list --resource-group $RG2NAME --query "[0].name" --output tsv) DEPLOYMENTURL=$(az webapp deployment source config-local-git --name $WEBAPPNAME --resource-group $RG2NAME --output tsv)
运行以下命令,配置名为 azure 的远程存储库,以表示上一步中标识的部署 URL:
git remote add azure $DEPLOYMENTURL
运行以下命令,基于 main 分支创建 test 分支,并将其内容推送到 Azure Web 应用 (当系统提示输入之前在本任务中记录的作为用户级部署凭据一部分的密码时):
git checkout -b test git commit -a -m "testing" git push --set-upstream azure test
关闭 Cloud Shell 窗格。
在 Incognito/InPrivate 模式下打开另一个 Web 浏览器窗口,导航到 Azure 门户,然后使用上一个练习中创建的 adatumuser1 用户帐户登录。
在 Azure 门户中,使用 Azure 门户页面顶部的“搜索资源、服务和文档”文本框搜索“应用服务”。
在“应用服务”边栏选项卡的应用服务实例列表中,选择表示新部署的 Azure Web 应用的条目。
在显示 Web 应用属性的边栏选项卡上的“概要”部分中,复制“默认域”URL 的值。
在同一浏览器窗口中打开另一个选项卡,在该选项卡的搜索框中,输入 https://,粘贴你刚刚复制的剪贴板的 URL,添加 /auth 作为后缀,然后按 Enter。
注意
该 URL 应采用以下格式:
https://<webapp_name>.azurewebsites.net/auth
验证生成的网页是否包含当前登录用户的 Microsoft Entra 身份验证信息(输出可能不同)。
结果
祝贺你! 你已完成了本模块的第二个练习。 在本练习中,你在单一服务器部署模型中安装了 Azure Database for PostgreSQL,使用示例清单数据创建了一个数据库,将服务器与 Microsoft Entra ID 集成,并实现了一个基于 Node.js 的简单应用程序,该应用程序依靠 Microsoft Entra 身份验证来查询数据库。