Azure SignalR 服务身份验证

本教程继续介绍使用 SignalR 服务创建聊天室中介绍的聊天室应用程序。 首先完成快速入门以设置聊天室。

本教程介绍如何使用 Azure SignalR服务创建和集成身份验证方法。

对于实际方案而言,最初在快速入门聊天室应用程序中使用的身份验证太过简单。 应用程序允许每个客户端声明自己的身份,服务器只需接受即可。 这种方法在现实世界中无效,因为恶意用户可以使用假标识来访问敏感数据。

GitHub 提供基于常用行业标准协议(名为 OAuth)的身份验证 API。 这些 API 允许第三方应用程序对 GitHub 帐户进行身份验证。 在本教程中,你可以先使用这些 API 通过 GitHub 帐户实现身份验证,然后再允许客户端登录到聊天室应用程序。 对 GitHub 帐户进行身份验证后,帐户信息将添加为 Web 客户端用来进行身份验证的 cookie。

若要深入了解通过 GitHub 提供的 OAuth 身份验证 API,请参阅 Basics of Authentication(身份验证基础知识)。

可使用任何代码编辑器来完成本快速入门中的步骤。 但是,Visual Studio Code 是一个很好的选项,可用于 Windows、macOS 和 Linux 平台。

本教程所用代码可在 AzureSignalR-samples GitHub 存储库下载。

OAuth Complete hosted in Azure

本教程介绍如何执行下列操作:

  • 使用 GitHub 帐户注册新的 OAuth 应用
  • 添加身份验证控制器以支持 GitHub 身份验证
  • 将 ASP.NET Core Web 应用部署到 Azure

如果没有 Azure 试用版订阅,请在开始前创建一个试用版订阅

先决条件

若要完成本教程,必须满意以下先决条件:

创建 OAuth 应用

  1. 打开 web 浏览器,导航到 https://github.com 并登录帐户。

  2. 对于帐户,导航到“设置”>“开发人员设置”>“OAuth 应用”,然后在“OAuth 应用”下选择“新建 OAuth 应用”

  3. 为新 OAuth 应用使用以下设置,然后选择“注册应用程序”:

    设置名称 建议的值 说明
    应用程序名称 Azure SignalR 聊天 GitHub 用户应能识别并信任他们要用于身份验证的应用。
    主页 URL https://localhost:5001
    应用程序说明 配合使用 Azure SignalR 服务和 GitHub 身份验证的聊天室示例 有效的应用程序说明可帮助应用程序用户理解所用的身份验证上下文。
    授权回调 URL https://localhost:5001/signin-github 这是 OAuth 应用程序最重要的设置。 它是身份验证成功后 GitHub 返回用户的回调 URL。 在本教程中,必须使用 AspNet.Security.OAuth.GitHub 包的默认回调 URL“/signin-github” 。
  4. 新的 OAuth 应用注册完成后,使用以下命令将客户端 ID 和客户端密码添加到机密管理器 。 将 Your_GitHub_Client_Id 和 Your_GitHub_Client_Secret 替换为 OAuth 应用的值 。

    dotnet user-secrets set GitHubClientId Your_GitHub_Client_Id
    dotnet user-secrets set GitHubClientSecret Your_GitHub_Client_Secret
    

实现 OAuth 流

让我们重用在教程使用 SignalR 服务创建聊天室中创建的聊天应用。

更新 Program.cs 以支持 GitHub 身份验证

  1. 添加对最新 AspNet.Security.OAuth.GitHub 包的引用并还原所有包

    dotnet add package AspNet.Security.OAuth.GitHub
    
  2. 打开 Program.cs,将代码更新为以下代码片段

    using Microsoft.AspNetCore.Authentication.Cookies;
    using Microsoft.AspNetCore.Authentication.OAuth;
    
    using System.Net.Http.Headers;
    using System.Security.Claims;
    
    var builder = WebApplication.CreateBuilder(args);
    
    builder.Services
        .AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
        .AddCookie()
        .AddGitHub(options =>
        {
            options.ClientId = builder.Configuration["GitHubClientId"] ?? "";
            options.ClientSecret = builder.Configuration["GitHubClientSecret"] ?? "";
            options.Scope.Add("user:email");
            options.Events = new OAuthEvents
            {
                OnCreatingTicket = GetUserCompanyInfoAsync
            };
        });
    
    builder.Services.AddControllers();
    builder.Services.AddSignalR().AddAzureSignalR();
    
    var app = builder.Build();
    
    app.UseHttpsRedirection();
    app.UseDefaultFiles();
    app.UseStaticFiles();
    
    app.UseRouting();
    
    app.UseAuthorization();
    
    app.MapControllers();
    app.MapHub<ChatSampleHub>("/chat");
    
    app.Run();
    
    static async Task GetUserCompanyInfoAsync(OAuthCreatingTicketContext context)
    {
        var request = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint);
        request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);
    
        var response = await context.Backchannel.SendAsync(request,
            HttpCompletionOption.ResponseHeadersRead, context.HttpContext.RequestAborted);
        var user = await response.Content.ReadFromJsonAsync<GitHubUser>();
        if (user?.company != null)
        {
            context.Principal?.AddIdentity(new ClaimsIdentity(new[]
            {
                new Claim("Company", user.company)
            }));
        }
    }
    
    class GitHubUser
    {
        public string? company { get; set; }
    }
    

    在该代码中,AddAuthenticationUseAuthentication 用于添加 GitHub OAuth 应用的身份验证支持,GetUserCompanyInfoAsync 帮助程序方法是示例代码,演示如何从 GitHub OAuth 加载公司信息并将其保存到用户标识中。 你可能还会注意到,由于 GitHub OAuth 设置了仅传递到安全 https 方案的 secure cookie,因此使用了 UseHttpsRedirection()。 此外,不要忘记更新本地 Properties/lauchSettings.json 以添加 https 终结点:

    {
      "profiles": {
        "GitHubChat" : {
          "commandName": "Project",
          "launchBrowser": true,
          "environmentVariables": {
            "ASPNETCORE_ENVIRONMENT": "Development"
          },
          "applicationUrl": "http://0.0.0.0:5000/;https://0.0.0.0:5001/;"
        }
      }
    }
    

添加身份验证控制器

在本部分中实现 Login API,它使用 GitHub OAuth 应用对客户端进行身份验证。 进行身份验证后,API 会在将客户端重定向回聊天应用前向 Web 客户端响应添加 cookie。 该 cookie 稍后用于标识客户端。

  1. 将新的控制器代码文件添加到 GitHubChat\Controllers 目录。 将文件命名为 AuthController.cs。

  2. 为身份验证控制器添加以下代码。 如果项目目录不是 GitHubChat,请务必更新命名空间

    using AspNet.Security.OAuth.GitHub;
    
    using Microsoft.AspNetCore.Authentication;
    using Microsoft.AspNetCore.Mvc;
    
    namespace GitHubChat.Controllers
    {
        [Route("/")]
        public class AuthController : Controller
        {
            [HttpGet("login")]
            public IActionResult Login()
            {
                if (User.Identity == null || !User.Identity.IsAuthenticated)
                {
                    return Challenge(GitHubAuthenticationDefaults.AuthenticationScheme);
                }
    
                HttpContext.Response.Cookies.Append("githubchat_username", User.Identity.Name ?? "");
                HttpContext.SignInAsync(User);
                return Redirect("/");
            }
        }
    }
    
  3. 保存所做的更改。

更新集线器类

默认情况下,Web 客户端使用 Azure SignalR SDK 生成的访问令牌连接到 SignalR 服务。

在本部分中,通过向集线器类添加 Authorize 属性,并将集线器方法更新为从经身份验证的用户声明中读取用户名,集成真实身份验证工作流。

  1. 打开 Hub\ChatSampleHub.cs,将代码更新为以下代码片段。 此代码将 Authorize 属性添加到 ChatSampleHub 类,并在中心方法中使用用户经过身份验证的标识。 此外,还添加了 OnConnectedAsync 方法,该方法会在每次出现新客户端连接时将系统消息记录到聊天室。

    using Microsoft.AspNetCore.Authorization;
    using Microsoft.AspNetCore.SignalR;
    
    [Authorize]
    public class ChatSampleHub : Hub
    {
        public override Task OnConnectedAsync()
        {
            return Clients.All.SendAsync("broadcastMessage", "_SYSTEM_", $"{Context.User?.Identity?.Name} JOINED");
        }
    
        // Uncomment this line to only allow user in Microsoft to send message
        //[Authorize(Policy = "Microsoft_Only")]
        public Task BroadcastMessage(string message)
        {
            return Clients.All.SendAsync("broadcastMessage", Context.User?.Identity?.Name, message);
        }
    
        public Task Echo(string message)
        {
            var echoMessage = $"{message} (echo from server)";
            return Clients.Client(Context.ConnectionId).SendAsync("echo", Context.User?.Identity?.Name, echoMessage);
        }
    }
    
  2. 保存所做更改。

更新 Web 客户端代码

  1. 打开 wwwroot\index.html,将提示用户名的代码替换为使用身份验证控制器返回的 cookie 的代码。

    将 index.html 中函数 getUserName 内的代码更新为以下内容,以使用 cookie

    function getUserName() {
      // Get the user name cookie.
      function getCookie(key) {
        var cookies = document.cookie.split(";").map((c) => c.trim());
        for (var i = 0; i < cookies.length; i++) {
          if (cookies[i].startsWith(key + "="))
            return unescape(cookies[i].slice(key.length + 1));
        }
        return "";
      }
      return getCookie("githubchat_username");
    }
    
  2. 更新 onConnected 函数,以在调用中心方法 broadcastMessageecho 时移除 username 参数:

    function onConnected(connection) {
      console.log("connection started");
      connection.send("broadcastMessage", "_SYSTEM_", username + " JOINED");
      document.getElementById("sendmessage").addEventListener("click", function (event) {
        // Call the broadcastMessage method on the hub.
        if (messageInput.value) {
          connection.invoke("broadcastMessage", messageInput.value)
            .catch((e) => appendMessage("_BROADCAST_", e.message));
        }
    
        // Clear text box and reset focus for next comment.
        messageInput.value = "";
        messageInput.focus();
        event.preventDefault();
      });
      document.getElementById("message").addEventListener("keypress", function (event) {
        if (event.keyCode === 13) {
          event.preventDefault();
          document.getElementById("sendmessage").click();
          return false;
        }
      });
      document.getElementById("echo").addEventListener("click", function (event) {
        // Call the echo method on the hub.
        connection.send("echo", messageInput.value);
    
        // Clear text box and reset focus for next comment.
        messageInput.value = "";
        messageInput.focus();
        event.preventDefault();
      });
    }
    
  3. 在 index.html 底部,更新 connection.start() 的错误处理程序(如下所示),提示用户进行登录。

    connection.start()
      .then(function () {
        onConnected(connection);
      })
      .catch(function (error) {
        console.error(error.message);
        if (error.statusCode && error.statusCode === 401) {
          appendMessage(
            "_BROADCAST_",
            "You\"re not logged in. Click <a href="/login">here</a> to login with GitHub."
          );
        }
      });
    
  4. 保存所做更改。

在本地生成并运行应用

  1. 保存对所有文件的更改。

  2. 执行以下命令,以在本地运行 Web 应用:

    dotnet run
    

    默认情况下,应用将本地托管在端口 5000 上:

    info: Microsoft.Hosting.Lifetime[14]
          Now listening on: http://0.0.0.0:5000
    info: Microsoft.Hosting.Lifetime[14]
          Now listening on: https://0.0.0.0:5001
    info: Microsoft.Hosting.Lifetime[0]
          Application started. Press Ctrl+C to shut down.
    info: Microsoft.Hosting.Lifetime[0]
          Hosting environment: Development
    
  3. 启动浏览器窗口并导航到 https://localhost:5001。 选择顶部的“此处”链接,使用 GitHub 进行登录。

    OAuth Complete hosted in Azure

    系统会提示授予聊天应用访问 GitHub 帐户的权限。 选择“授权”按钮。

    Authorize OAuth App

    将重定向回聊天应用程序并使用 GitHub 帐户名称进行登录。 Web 应用程序使用添加的新身份验证进行身份验证,从而确定帐户名称。

    Account identified

    由于聊天应用现在使用 GitHub 执行身份验证并将身份验证信息存储为 Cookie,接下来需要将其部署到 Azure。 此方法让其他用户能够使用各自的帐户进行身份验证,并从各种工作站进行通信。

将应用部署到 Azure

为 Azure CLI 准备环境:

如需在本地运行 CLI 参考命令,请安装 Azure CLI。 如果在 Windows 或 macOS 上运行,请考虑在 Docker 容器中运行 Azure CLI。 有关详细信息,请参阅如何在 Docker 容器中运行 Azure CLI

  • 如果使用的是本地安装,请使用 az login 命令登录到 Azure CLI。 若要完成身份验证过程,请遵循终端中显示的步骤。 有关其他登录选项,请参阅使用 Azure CLI 登录

  • 出现提示时,请在首次使用时安装 Azure CLI 扩展。 有关扩展详细信息,请参阅使用 Azure CLI 的扩展

  • 运行 az version 以查找安装的版本和依赖库。 若要升级到最新版本,请运行 az upgrade

在本部分中,使用 Azure CLI 在 Azure 应用服务中创建新的 Web 应用,以便在 Azure 中托管 ASP.NET 应用程序。 Web 应用配置为使用本地 Git 部署。 还会使用 SignalR 连接字符串、GitHub OAuth 应用机密和部署用户配置 Web 应用。

创建以下资源时,请确保使用的资源组与 SignalR 服务资源驻留的资源组相同。 稍后若要移除所有资源,通过此方法可更轻松地进行清理。 给定示例假定使用之前教程中建议的组名称 SignalRTestResources。

创建 Web 应用和计划

复制下面命令的文本并更新参数。 将更新的脚本粘贴到 Azure CLI,然后按 Enter 创建新的应用服务计划和 Web 应用

#========================================================================
#=== Update these variable for your resource group name.              ===
#========================================================================
ResourceGroupName=SignalRTestResources

#========================================================================
#=== Update these variable for your web app.                          ===
#========================================================================
WebAppName=myWebAppName
WebAppPlan=myAppServicePlanName

# Create an App Service plan.
az appservice plan create --name $WebAppPlan --resource-group $ResourceGroupName \
    --sku FREE

# Create the new Web App
az webapp create --name $WebAppName --resource-group $ResourceGroupName \
    --plan $WebAppPlan
参数 说明
ResourceGroupName 这是之前教程中建议的资源组名称。 将所有教程资源聚集在一起是一个好办法。 使用在之前教程中使用的相同资源组。
WebAppPlan 输入一个新的、唯一的应用服务计划名称。
WebAppName 此参数是新 Web 应用的名称,也是 URL 的一部分。 让它具有唯一性。 例如,signalrtestwebapp22665120。

将应用设置添加到 Web 应用

在本部分中,添加以下组件的应用设置:

  • SignalR 服务资源连接字符串
  • GitHub OAuth 应用客户端 ID
  • GitHub OAuth 应用客户端密码

复制下面命令的文本并更新参数。 将更新的脚本粘贴到 Azure CLI 中,然后按 Enter 添加应用设置

#========================================================================
#=== Update these variables for your GitHub OAuth App.                ===
#========================================================================
GitHubClientId=1234567890
GitHubClientSecret=1234567890

#========================================================================
#=== Update these variables for your resources.                       ===
#========================================================================
ResourceGroupName=SignalRTestResources
SignalRServiceResource=mySignalRresourcename
WebAppName=myWebAppName

# Get the SignalR primary connection string
primaryConnectionString=$(az signalr key list --name $SignalRServiceResource \
  --resource-group $ResourceGroupName --query primaryConnectionString -o tsv)

#Add an app setting to the web app for the SignalR connection
az webapp config appsettings set --name $WebAppName \
    --resource-group $ResourceGroupName \
    --settings "Azure__SignalR__ConnectionString=$primaryConnectionString"

#Add the app settings to use with GitHub authentication
az webapp config appsettings set --name $WebAppName \
    --resource-group $ResourceGroupName \
    --settings "GitHubClientId=$GitHubClientId"
az webapp config appsettings set --name $WebAppName \
    --resource-group $ResourceGroupName \
    --settings "GitHubClientSecret=$GitHubClientSecret"
参数 说明
GitHubClientId 为此变量分配 GitHub OAuth 应用的机密客户端 ID。
GitHubClientSecret 为此变量分配 GitHub OAuth 应用的机密密码。
ResourceGroupName 将此变量更新为在上一部分中使用的相同资源组名称。
SignalRServiceResource 使用快速入门中创建的 SignalR 服务资源名称更新此变量。 例如,signalrtestsvc48778624。
WebAppName 使用上一部分中创建的新 Web 应用名称更新此变量。

为本地 Git 部署配置 Web 应用

在 Azure CLI 中,粘贴以下脚本。 此脚本创建新的部署用户名和密码,使用 Git 将代码部署到 Web 应用时会使用该用户名和密码。 该脚本还使用本地 Git 存储库配置用于部署的 Web 应用,并返回 Git 部署 URL。

#========================================================================
#=== Update these variables for your resources.                       ===
#========================================================================
ResourceGroupName=SignalRTestResources
WebAppName=myWebAppName

#========================================================================
#=== Update these variables for your deployment user.                 ===
#========================================================================
DeploymentUserName=myUserName
DeploymentUserPassword=myPassword

# Add the desired deployment user name and password
az webapp deployment user set --user-name $DeploymentUserName \
    --password $DeploymentUserPassword

# Configure Git deployment and note the deployment URL in the output
az webapp deployment source config-local-git --name $WebAppName \
    --resource-group $ResourceGroupName \
    --query [url] -o tsv
参数 说明
DeploymentUserName 选择新的部署用户名。
DeploymentUserPassword 为新的部署用户选择密码。
ResourceGroupName 使用上一部分中使用的相同资源组名称。
WebAppName 此参数是之前创建的新 Web 应用的名称。

记下此命令返回的 Git 部署 URL。 稍后会用到此 URL。

将代码部署到 Azure Web 应用

若要部署代码,请在 Git shell 中执行以下命令。

  1. 导航到项目目录的根目录。 如果未使用 Git 存储库初始化该项目,请执行以下命令:

    git init
    
  2. 为前面记下的 Git 部署 URL 添加远程:

    git remote add Azure <your git deployment url>
    
  3. 将所有文件暂存在初始化存储库中并添加提交。

    git add -A
    git commit -m "init commit"
    
  4. 将代码部署到 Azure 中的 Web 应用。

    git push Azure main
    

    系统会提示进行身份验证以便将代码部署到 Azure。 输入上面创建的部署用户的用户名和密码。

更新 GitHub OAuth 应用

需要执行的最后一步是更新 GitHub OAuth 应用的“主页 URL”和“授权回调 URL”,指向新的托管应用 。

  1. 在浏览器中打开 https://github.com 并导航到帐户的“设置”>“开发人员设置”>“Oauth 应用”。

  2. 选择身份验证应用并更新“主页 URL”和“授权回叫 URL”,如下所示:

    设置 示例
    主页 URL https://signalrtestwebapp22665120.chinacloudsites.cn
    授权回调 URL https://signalrtestwebapp22665120.chinacloudsites.cn/signin-github
  3. 导航到 Web 应用 URL,并测试应用程序。

    OAuth Complete hosted in Azure

清理资源

如果还要继续下一教程,可保留在此快速入门中创建的资源,并在下一教程中重复使用。

如果已完成快速入门示例应用程序,可以删除本快速入门中创建的 Azure 资源,以免产生费用。

重要

删除资源组的操作不可逆,资源组以及其中的所有资源将被永久删除。 请确保不会意外删除错误的资源组或资源。 如果在现有资源组(其中包含要保留的资源)中为托管此示例而创建了相关资源,可从各自的边栏选项卡逐个删除这些资源,而不要删除资源组。

登录到 Azure 门户,然后选择“资源组”。

在“按名称筛选...”文本框中键入资源组的名称。 本文的说明使用名为“SignalRTestResources”的资源组。 在结果列表中的资源组上,单击“...”,然后单击“删除资源组” 。

Delete

系统会要求确认是否删除资源组。 键入资源组的名称进行确认,然后选择“删除”。

片刻之后,将会删除该资源组及其包含的所有资源。

后续步骤

在本教程中,借助 OAuth 添加身份验证,为使用 Azure SignalR 服务进行身份验证提供了更好的方法。 若要了解有关使用 Azure SignalR 服务器的详细信息,请继续了解用于 SignalR 服务的 Azure CLI 示例。