如何用MFA保护您的.NET 5 Blazor服务器应用程序(附代码)

140 阅读8分钟

服务器Blazor应用程序简介

Blazor是微软推出的一项令人兴奋的新技术,它将允许开发人员将C#带到客户端。服务器和客户端组件是用相同的语言编写的,可以互换使用和重用。Blazor有两种风格,即服务器和客户端应用程序。在本教程中,您将使用服务器Blazor应用程序,其中C#代码在服务器上运行,并使用SignalR

我将向您展示如何将Okta与服务器端的Blazor应用程序集成。然后你将学习如何使用Okta的多因素认证系统来提供一个额外的安全层。多因素认证(MFA)是一种技术,在这种技术中,用户必须出示两个或两个以上的凭证,才能成功获得访问权,完成登录或其他交易。 有许多方法可以做到这一点。 最常见的是在用户被认证后,通过电子邮件或短信向用户发送一个代码。 然后,用户可以验证该代码,从而确定该用户知道适当的凭证,但也可以访问记录中的电话号码或电子邮件地址。

你需要的东西

创建您的Okta应用程序

使用Okta CLI,为.NET Blazor创建一个新的Okta应用程序。或者,使用您的Okta管理门户来完成这项工作。导航到应用程序,点击Create App Integration ,为Sign in Type 选择OIDC,为Application Type 选择Web,并为您的本地主机环境填写具体内容。

在你开始之前,你需要一个免费的Okta开发者账户。安装Okta CLI并运行okta register 来注册一个新的账户。如果你已经有一个账户,运行okta login 。然后,运行okta apps create 。选择默认的应用程序名称,或根据你的需要进行更改。 选择Web,然后按回车键

选择ASP.NET Core。 然后,将重定向URI改为https://localhost:44378/authorization-code/callback ,并使用https://localhost:44378/signout/callback ,作为注销重定向URI。

Okta CLI是做什么的?

Okta CLI将在您的Okta机构中创建一个OIDC网络应用。它将添加您指定的重定向URI,并授予Everyone组的访问权限。当它完成后,您会看到如下输出:

Okta application configuration has been written to: /path/to/app/.okta.env

运行cat .okta.env (或Windows上的type .okta.env ),查看你的应用程序的发行者和凭证:

export OKTA_OAUTH2_ISSUER="https://dev-133337.okta.com/oauth2/default"
export OKTA_OAUTH2_CLIENT_ID="0oab8eb55Kb9jdMIr5d6"
export OKTA_OAUTH2_CLIENT_SECRET="NEVER-SHOW-SECRETS"

您的Okta域是发行者的第一部分,在/oauth2/default

注意:你也可以使用Okta管理控制台来创建你的应用程序。更多信息请参见创建一个ASP.NET Core应用程序

一旦您完成了Okta应用程序的设置,您将需要启用多因素认证,然后在您的应用程序上启用它。现在,导航到管理门户,在应用程序下选择您刚刚创建的应用程序。点击 "登录",在 "登录策略"部分点击 "添加规则"。

在 "访问"部分,点击 "多因素设置"。对于这个应用,您将只启用短信验证,但您可以在这个页面看到Okta允许的因素类型。在SMS认证标签下,将下拉菜单设置为激活

返回到规则页面,点击提示因素,然后点击每次登录。给您的新规则起个名字,然后点击保存。该登录策略现在将对任何试图登录该应用程序的人进行触发。

创建你的Blazor应用程序

打开Visual Studio 2019,选择创建一个新项目。在创建项目页面上找到Blazor应用程序条目。 请注意,如果您使用的是不同版本的Visual Studio,您的项目创建模板可能看起来有所不同。

创建一个新的Blazor应用部分,选择Blazor服务器应用,并给它一分钟时间进行设置。

一旦完成,您将看到配置您的新项目屏幕。 将您的项目命名为Okta_ServerDemo,然后按创建

设置完成后,花一分钟时间运行该应用程序,看看Blazor是如何设置的。对于本教程来说,Blazor设置中附带的演示应用程序就足够了。不过我们需要对该应用程序做一些修改。

设置您的应用程序

首先,通过命令行或NuGet包管理器从NuGet安装Okta.AspNetCore 包。这将使与Okta的集成变得快速而简单:

Install-Package Okta.AspNetCore -Version 4.0.0

接下来,打开appsettings.Development.json ,用下面的内容编辑代码:

{
  "DetailedErrors": true,
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "Okta": {
    "Domain": "{yourOktaDomain}",
    "ClientId": "{yourClientId}}",
    "ClientSecret": "{yourClientSecret}"
  }
}

如果你不知道在哪里可以找到Okta的设置值,你应该可以在.okta.env 。你也可以从Okta管理控制台检索到它们。

现在你可以改变你的Startup.cs ,用Okta配置认证:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Okta_ServerDemo.Data;
using System;
using System.Collections.Generic;
using Okta.AspNetCore;

using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;

namespace Okta_ServerDemo
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddRazorPages();
            services.AddServerSideBlazor();
            services.AddSingleton<WeatherForecastService>();

            services.AddAuthentication(options =>
            {
                options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
            })
           .AddCookie()
           .AddOktaMvc(new OktaMvcOptions
           {
               OktaDomain = Configuration.GetValue<string>("Okta:Domain"),
               ClientId = Configuration.GetValue<string>("Okta:ClientId"),
               ClientSecret = Configuration.GetValue<string>("Okta:ClientSecret"),
               Scope = new List<string> { "openid", "profile", "email" },
           });
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();

            app.UseStaticFiles();

            app.UseRouting();

            app.UseAuthentication();
            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapBlazorHub();
                endpoints.MapFallbackToPage("/_Host");
            });
        }
    }
}

编写你的应用程序

接下来,你可以把注意力转移到页面上。首先,你要用以下代码编辑App.razor

<CascadingAuthenticationState>
    <Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">
        <Found Context="routeData">
            <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
                <Authorizing>
                    <p>Determining session state, please wait...</p>
                </Authorizing>
                <NotAuthorized>
                    <h1>Sorry</h1>
                    <p>You're not authorized to reach this page. You need to log in.</p>

                    <UserComponent></UserComponent>
                </NotAuthorized>
            </AuthorizeRouteView>
        </Found>
        <NotFound>
            <LayoutView Layout="@typeof(MainLayout)">
                <p>Sorry, there's nothing at this address.</p>
            </LayoutView>
        </NotFound>
    </Router>
</CascadingAuthenticationState>

这个页面的大部分内容是设置过程中的模板。但你需要做几个重要的改动。首先,将整个代码包裹在CascadingAuthenticationState 元素中。这将把认证状态级联到你的每个路由。接下来,你需要将RouteView 改为AuthorizeView ,并处理AuthorizingNotAuthorized 的状态。

在这一点上,你可能已经注意到,UserComponent.razor 并不存在,所以现在在Shared 文件夹中创建它。在其中添加以下代码:

<AuthorizeView>
    <Authorized>
        <a href="logout">Log out</a>
    </Authorized>
    <NotAuthorized>
        <a href="login?redirectUri=/">Log in</a>
    </NotAuthorized>
</AuthorizeView>

这个组件利用了上面的CascadingAuthenticationState ,并展示了两个不同的视图。如果用户已经登录,那么你就显示一个注销按钮。如果用户是未授权的,则向他们展示一个登录按钮。

这里的两个路由,loginlogout ,接下来需要实现。在页面下,添加一个新的RazorPage ,名为Login 。Razor页面的创建有两个组件,一个是cshtml 文件,一个是.cs 文件。由于你的登录页面是由Okta托管的,你不需要在.cshtml 文件中进行任何展示。然而,你仍然需要告诉页面使用哪个模型文件。打开Login.cshtml ,添加以下代码:

@page
@model Okta_ServerDemo.Pages.LoginModel


@{
   
}

接下来,打开Login.cshtml.cs ,添加登录逻辑:

using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Authentication;
using Okta.AspNetCore;

namespace Okta_ServerDemo.Pages
{
    public class LoginModel : PageModel
    {
        public async Task OnGet(string redirectUri)
        {
            await HttpContext.ChallengeAsync(OktaDefaults.MvcAuthenticationScheme, new AuthenticationProperties
            {
                RedirectUri = redirectUri
            });
        }
    }
}

如果你在任何ASP.NET应用程序中使用过Okta,这段代码应该看起来很熟悉。你只是返回Okta提供的Challenge 。这将把用户重定向到Okta的登录页面。

在你认证之后,你会被要求提供你的电话号码,或者被提示向你的手机发送一个代码。这取决于您是否已经在Okta中设置了您的手机。如果您没有,请将您的电话号码添加到您的账户中并验证代码;否则,请点击发送代码,然后在此页面验证代码。就这样,您的应用程序现在可以使用Okta的MFA来保证安全了你可以选择Okta网站上的几种MFA方法,从电子邮件和电话代码到物理验证器。

现在你可以像添加Login 页面一样添加Logout 页面。同样,由于这里没有演示,你的.cshtml 文件将声明模型页面,没有任何HTML:

@page
@model Okta_ServerDemo.Pages.LogoutModel
@{
}

你的Logout.cshtml.cs 文件将包含库存的ASP.Net Core注销逻辑:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Okta.AspNetCore;

namespace Okta_ServerDemo.Pages
{
    public class LogoutModel : PageModel
    {
        public SignOutResult OnGet()
        {
            return new SignOutResult(
                new[]
                {
                     OktaDefaults.MvcAuthenticationScheme,
                     CookieAuthenticationDefaults.AuthenticationScheme,
                },
                new AuthenticationProperties { RedirectUri = "/" });
        }
    }
}

最后,CounterFetchData 页面将需要用户认证。

打开Counter.razor ,添加@attribute [Authorize] 注释:

@page "/counter"

@attribute [Authorize]

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
    }
}

这段代码几乎与模板代码相同,除了页面顶部的Authorize 属性。像ASP.Net Core一样,这个属性将要求用户在访问它之前登录。

同样地,用下面的代码编辑FetchData.razor 文件:

@page "/fetchdata"

@using Okta_ServerDemo.Data
@inject WeatherForecastService ForecastService

@attribute [Authorize]

<h1>Weather forecast</h1>

<p>This component demonstrates fetching data from a service.</p>

@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>Date</th>
                <th>Temp. (C)</th>
                <th>Temp. (F)</th>
                <th>Summary</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var forecast in forecasts)
            {
                <tr>
                    <td>@forecast.Date.ToShortDateString()</td>
                    <td>@forecast.TemperatureC</td>
                    <td>@forecast.TemperatureF</td>
                    <td>@forecast.Summary</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    private WeatherForecast[] forecasts;

    protected override async Task OnInitializedAsync()
    {
        forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
    }
}

同样,这里唯一的变化是增加了Authorize 属性。

测试你的应用程序

现在启动应用程序并导航到Counter 页面。

你会看到在你的App.razor 页面上发现的信息。点击登录按钮,走完认证和MFA过程。一旦你登录了,你应该看到`**页面。

就是这样!如果你想看一下这个解决方案的全部内容,请查看GitHub repo

了解更多关于与Blazor合作的信息

有了Okta,用MFA保护您的应用程序比以往任何时候都要容易。只需改变一些配置,Okta就能完成繁重的工作,为您的Blazor服务器端应用增加一层安全保障。