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

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

先决条件

在 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
    

默认情况下,生成的项目包含一个 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 "SignalR negotiate HTTP trigger" --name negotiate
    
  2. 打开 negotiate/function.json 以查看函数绑定配置。

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

    {
      "disabled": false,
      "bindings": [
        {
          "authLevel": "anonymous",
          "type": "httpTrigger",
          "direction": "in",
          "methods": ["post"],
          "name": "req",
          "route": "negotiate"
        },
        {
          "type": "http",
          "direction": "out",
          "name": "res"
        },
        {
          "type": "signalRConnectionInfo",
          "name": "connectionInfo",
          "hubName": "default",
          "connectionStringSetting": "AzureSignalRConnectionString",
          "direction": "in"
        }
      ]
    }
    

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

  3. 关闭 negotiate/function.json 文件。

  4. 打开 negotiate/index.js 以查看函数的正文:

    module.exports = async function (context, req, connectionInfo) {
      context.res.body = connectionInfo;
    };
    

    此函数从输入绑定中提取 SignalR 连接信息,并在 HTTP 响应正文中将此信息返回给客户端。 SignalR 客户端使用此信息连接到 Azure SignalR 服务实例。

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

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

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

    func new --name sendMessage --template "Http trigger"
    
  2. 若要为函数配置绑定,请将 sendMessage/function.json 的内容替换为以下代码:

    {
      "disabled": false,
      "bindings": [
        {
          "authLevel": "anonymous",
          "type": "httpTrigger",
          "direction": "in",
          "name": "req",
          "route": "messages",
          "methods": ["post"]
        },
        {
          "type": "http",
          "direction": "out",
          "name": "res"
        },
        {
          "type": "signalR",
          "name": "$return",
          "hubName": "default",
          "direction": "out"
        }
      ]
    }
    

    前面的代码会对原始文件做出两项更改:

    • 它将路由更改为 messages,并将 HTTP 触发器限制为 POST HTTP 方法。
    • 它添加一个 Azure SignalR 服务输出绑定,用于将函数返回的消息发送到已连接至名为 default 的 Azure SignalR 服务中心的所有客户端。
  3. 将 sendMessage/index.js 的内容替换为以下代码:

    module.exports = async function (context, req) {
      const message = req.body;
      message.sender =
        (req.headers && req.headers["x-ms-client-principal-name"]) || "";
    
      let recipientUserId = "";
      if (message.recipient) {
        recipientUserId = message.recipient;
        message.isPrivate = true;
      }
    
      return {
        userId: recipientUserId,
        target: "newMessage",
        arguments: [message],
      };
    };
    

    此函数从 HTTP 请求中提取正文,并将其发送到连接到 Azure SignalR 服务的客户端。 它在每个客户端上调用名为 newMessage 的函数。

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

  4. 保存文件。

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

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

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

  2. 在 content 文件夹中创建名为 index.html 的文件。

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

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

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

    const fs = require("fs");
    
    module.exports = async function (context, req) {
      const fileContent = fs.readFileSync("content/index.html", "utf8");
    
      context.res = {
        // status: 200, /* Defaults to 200 */
        body: fileContent,
        headers: {
          "Content-Type": "text/html",
        },
      };
    };
    

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

  6. 打开 index/function.json,将绑定的 authLevel 值更改为 anonymous 现在,整个文件如下示例所示:

    {
      "bindings": [
        {
          "authLevel": "anonymous",
          "type": "httpTrigger",
          "direction": "in",
          "name": "req",
          "methods": ["get", "post"]
        },
        {
          "type": "http",
          "direction": "out",
          "name": "res"
        }
      ]
    }
    
  7. 在本地测试你的应用。 使用以下命令启动函数应用:

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

    Screenshot of a web user interface for a local chat client.

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

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

部署到 Azure 并启用身份验证

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

为函数应用配置身份验证

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

  1. 打开 negotiate/function.json。

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

    {
      "type": "signalRConnectionInfo",
      "name": "connectionInfo",
      "userId": "{headers.x-ms-client-principal-name}",
      "hubName": "default",
      "direction": "in"
    }
    
  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 帐户进行身份验证。

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

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

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

    Screenshot of the function app Authentication page and the button for adding an identity provider.

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

    Screenshot of the page for adding an identity provider.

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

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

尝试运行应用程序

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

Screenshot of an authenticated online client chat app.

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

清理资源

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

注意

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

后续步骤

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