教程:使用 Azure Functions 进行 Azure SignalR 服务身份验证

在本分步教程中,你将使用以下技术构建包含身份验证和私密消息传送功能的聊天室:

注意

可以从 GitHub 获取本文中提到的代码。

先决条件

在 Azure 上创建必备资源

创建 Azure SignalR 服务资源

你的应用程序将访问 Azure SignalR 服务实例。 使用以下步骤在 Azure 门户中创建 Azure SignalR 服务实例。

  1. Azure 门户中,选择“创建资源”(+) 按钮。

  2. 搜索“SignalR 服务”并将其选中。

  3. 选择“创建”。

  4. 输入以下信息。

    名称
    资源组 创建具有唯一名称的新资源组。
    资源名称 为 Azure SignalR 服务实例输入唯一的名称。
    区域 选择附近的区域。
    定价层 选择“免费”。
    服务模式 选择“无服务器”
  5. 选择“查看 + 创建” 。

  6. 选择“创建” 。

创建 Azure 函数应用和 Azure 存储帐户

  1. 在 Azure 门户的主页上,选择“创建资源”(+)。

  2. 搜索“函数应用”并选择它。

  3. 选择“创建”。

  4. 输入以下信息。

    名称
    资源组 使用你的 Azure SignalR 服务实例所在的资源组。
    函数应用名称 为函数应用输入唯一的名称。
    运行时堆栈 选择“Node.js”
    区域 选择附近的区域。
  5. 默认情况下,会在同一资源组中随函数应用一起创建新的 Azure 存储帐户。 如果你想要在函数应用中使用其他存储帐户,请切换到“托管”选项卡以选择一个帐户

  6. 选择查看 + 创建,然后选择创建

在本地创建 Azure Functions 项目

初始化函数应用

  1. 在命令行中,为项目创建一个根文件夹并切换到该文件夹。

  2. 在终端中运行以下命令,以创建新的 JavaScript Functions 项目。

func init --worker-runtime node --language javascript --name my-app --model V4

默认情况下,生成的项目包含一个 host.json 文件,其中的扩展捆绑包包含 SignalR 扩展。 有关扩展捆绑包的详细信息,请参阅注册 Azure Functions 绑定扩展

配置应用程序设置

在本地运行和调试 Azure Functions 运行时时,函数应用将从 local.settings.json 读取应用程序设置。 使用 Azure SignalR 服务实例的连接字符串以及前面创建的存储帐户更新此文件。

将 local.settings.json 的内容替换为以下代码:

{
  "IsEncrypted": false,
  "Values": {
    "FUNCTIONS_WORKER_RUNTIME": "node",
    "AzureWebJobsStorage": "<your-storage-account-connection-string>",
    "AzureSignalRConnectionString": "<your-Azure-SignalR-connection-string>"
  }
}

在上述代码中:

  • AzureSignalRConnectionString 设置中输入 Azure SignalR 服务连接字符串。

    若要获取此字符串,请在 Azure 门户中转到你的 Azure SignalR 服务实例。 在“设置”部分,找到“密钥”设置。 选择连接字符串右侧的“复制”按钮,将连接字符串复制到剪贴板。 可以使用主要或辅助连接字符串。

  • AzureWebJobsStorage 设置中输入存储帐户连接字符串。

    若要获取此字符串,请在 Azure 门户中转到你的存储帐户。 在“安全 + 网络 ”部分中,找到“访问密钥”设置。 选择连接字符串右侧的“复制”按钮,将连接字符串复制到剪贴板。 可以使用主要或辅助连接字符串。

创建一个用于在 Azure SignalR 服务中验证用户身份的函数

当聊天应用在浏览器中首次打开时,需要使用有效的连接凭据连接到 Azure SignalR 服务。 在函数应用中创建一个名为 negotiate 的 HTTP 触发器函数,以返回此连接信息。

注意

此函数必须命名为 negotiate,因为 SignalR 客户端需要以 /negotiate 结尾的终结点。

  1. 在根项目文件夹中,使用以下命令基于内置模板创建 negotiate 函数:

    func new --template "HTTP trigger" --name negotiate
    
  2. 打开 src/functions/negotiate.js,按如下所示更新内容:

    const { app, input } = require('@azure/functions');
    
    const inputSignalR = input.generic({
        type: 'signalRConnectionInfo',
        name: 'connectionInfo',
        hubName: 'default',
        connectionStringSetting: 'AzureSignalRConnectionString',
    });
    
    app.post('negotiate', {
        authLevel: 'anonymous',
        handler: (request, context) => {
            return { body: JSON.stringify(context.extraInputs.get(inputSignalR)) }
        },
        route: 'negotiate',
        extraInputs: [inputSignalR],
    });
    

    该函数包含一个 HTTP 触发器绑定,用于接收来自 SignalR 客户端的请求。 该函数还包含一个 SignalR 输入绑定,用于生成有效的凭据,使客户端能够连接到名为 default 的 Azure SignalR 服务中心。

    此函数可从输入绑定中提取 SignalR 连接信息,并在 HTTP 响应正文中将此信息返回给客户端。

    对于本地开发,signalRConnectionInfo 绑定中没有 userId 属性。 稍后,在将函数应用部署到 Azure 时,你将添加该属性来设置 SignalR 连接的名称。

创建用于发送聊天消息的函数

Web 应用还需要使用一个 HTTP API 来发送聊天消息。 创建一个 HTTP 触发器函数,用于将消息发送到使用 Azure SignalR 服务的所有已连接客户端:

  1. 在根项目文件夹中,使用以下命令从模板创建名为 sendMessage 的 HTTP 触发器函数:

    func new --name sendMessage --template "Http trigger"
    
  2. 打开 src/functions/sendMessage.js 文件,按如下所示更新内容:

    const { app, output } = require('@azure/functions');
    
    const signalR = output.generic({
        type: 'signalR',
        name: 'signalR',
        hubName: 'default',
        connectionStringSetting: 'AzureSignalRConnectionString',
    });
    
    app.http('messages', {
        methods: ['POST'],
        authLevel: 'anonymous',
        extraOutputs: [signalR],
        handler: async (request, context) => {
            const message = await request.json();
            message.sender = request.headers && request.headers.get('x-ms-client-principal-name') || '';
    
            let recipientUserId = '';
            if (message.recipient) {
                recipientUserId = message.recipient;
                message.isPrivate = true;
            }
            context.extraOutputs.set(signalR,
                {
                    'userId': recipientUserId,
                    'target': 'newMessage',
                    'arguments': [message]
                });
        }
    });
    

    该函数包含 HTTP 触发器和 SignalR 输出绑定。 它可从 HTTP 请求中提取正文,并将其发送到与 Azure SignalR 服务连接的客户端。 它在每个客户端上调用名为 newMessage 的函数。

    该函数可以读取发送者的标识,并可以接受消息正文中的 recipient 值,以允许你以私密方式向单个用户发送消息。 稍后你将在本教程中使用这些功能。

  3. 保存文件。

托管聊天客户端 Web 用户界面

聊天应用程序的 UI 是通过 ASP.NET Core SignalR JavaScript 客户端使用 Vue JavaScript 框架创建的简单单页应用程序 (SPA)。

  1. 在函数项目的根目录中创建名为 index.html 的文件。

  2. 将 index.html 的内容复制并粘贴到该文件中。 保存文件。

  3. 在根项目文件夹中,使用以下命令基于模板创建名为 index 的 HTTP 触发器函数:

    func new --name index --template "Http trigger"
    
  4. 将 src/functions/index.js 的内容 修改为以下代码:

    const { app } = require('@azure/functions');
    const { readFile } = require('fs/promises');
    
    app.http('index', {
        methods: ['GET'],
        authLevel: 'anonymous',
        handler: async (context) => {
            const content = await readFile('index.html', 'utf8', (err, data) => {
                if (err) {
                    context.err(err)
                    return
                }
            });
    
            return {
                status: 200,
                headers: {
                    'Content-Type': 'text/html'
                },
                body: content,
            };
        }
    });
    

    该函数将读取静态网页并将其返回给用户。

  5. 在本地测试你的应用。 使用以下命令启动函数应用:

    func start
    
  6. 在 Web 浏览器中打开 http://localhost:7071/api/index。 应当会出现一个聊天网页。

    本地聊天客户端的 Web 用户界面的屏幕截图。

  7. 在聊天框中输入一条消息。

    选择 Enter 键后,该消息将显示在网页上。 由于未设置 SignalR 客户端的用户名,因此你将以匿名方式发送所有消息。

部署到 Azure 并启用身份验证

你目前为止一直在本地运行函数应用和聊天应用程序。 现在,将它们部署到 Azure,并启用身份验证和私密消息传送。

为函数应用配置身份验证

到目前为止,聊天应用程序以匿名方式工作。 在 Azure 中,你将使用应用服务身份验证来验证用户的身份。 将已经过身份验证的用户的用户 ID 或用户名传递给 SignalRConnectionInfo 绑定,以生成进行用户身份验证时所需的连接信息。

  1. 打开 src/functions/negotiate.js 文件。

  2. 将值为 {headers.x-ms-client-principal-name}userId 属性插入到 inputSignalR 绑定中。 此值是一个绑定表达式,用于将 SignalR 客户端的用户名设置为已经过身份验证的用户的名称。 绑定现在应如以下示例所示:

    const inputSignalR = input.generic({
        type: 'signalRConnectionInfo',
        name: 'connectionInfo',
        hubName: 'default',
        connectionStringSetting: 'AzureSignalRConnectionString',
        userId: '{headers.x-ms-client-principal-name}'
    });
    
  3. 保存文件。

将函数应用部署到 Azure

使用以下命令将函数应用部署到 Azure:

func azure functionapp publish <your-function-app-name> --publish-local-settings

--publish-local-settings 选项将 local.settings.json 文件中的本地设置发布到 Azure,因此你无需再次在 Azure 中配置这些设置。

启用应用服务身份验证

Azure Functions 支持使用 Microsoft Entra ID 和 Microsoft 帐户进行身份验证。 在本教程中,你将使用 Microsoft 作为标识提供者。

  1. 在 Azure 门户中,转到你的函数应用的资源页。

  2. 选择“设置”>“身份验证”。

  3. 选择“添加标识提供者”。

    用于添加标识提供者的函数应用“身份验证”页和按钮的屏幕截图。

  4. 从“标识提供者”列表中,选择“Microsoft”。 然后选择“添加” 。

    用于添加标识提供者的页面屏幕截图。

完成的设置将创建一个应用注册,它会将你的标识提供者与你的函数应用相关联。

有关支持的标识提供者的详细信息,请参阅以下文章:

尝试运行应用程序

  1. 打开 https://<YOUR-FUNCTION-APP-NAME>.chinacloudsites.cn/api/index
  2. 选择“登录”,使用所选的身份验证提供程序进行身份验证。
  3. 在主要聊天框中输入公共消息并发送这些消息。
  4. 选择聊天历史记录中的用户名来发送私密消息。 只有选定的接收者可以收到这些消息。

经过身份验证的联机客户端聊天应用的屏幕截图。

祝贺你! 你已部署了一个实时无服务器聊天应用。

清理资源

若要清理你在本教程中创建的资源,请使用 Azure 门户删除相应的资源组。

注意

删除资源组会删除其中包含的所有资源。 如果资源组包含超出本教程范围的资源,这些资源也将被删除。

后续步骤

本教程已介绍如何将 Azure Functions 与 Azure SignalR 服务配合使用。 接下来请详细了解如何使用 Azure Functions 的 Azure SignalR 服务绑定来构建实时无服务器应用程序。