Blazor 和 ASP.NET Core 中的身份验证详解





5.00/5 (6投票s)
如何在 Blazor 和 ASP.NET Core 中实现身份验证?
引言
在任何应用程序中,身份验证都是一个至关重要的考虑因素。因此,ASP.NET Core 本身就提供了机制来简化这一过程,使我们能够根据具体需求进行定制。
遗憾的是,在开发生命周期的各个阶段,这一关键过程经常被利益相关者低估,主要是因为开发人员倾向于优先考虑业务需求并努力满足要求。此外,关于此主题的文档通常是零散的,或者仅仅关注特定需求。因此,在本系列中,我们的目标是全面概述 ASP.NET 中的身份验证,提供对其复杂性和重要性的清晰理解。
以下书籍对本系列的总结有所助益。
Pro ASP.NET Core Identity (Freeman)
本文最初发布于: Blazor 和 ASP.NET Core 中的身份验证详解
什么是身份验证?
身份验证是验证尝试访问资源或服务的用户或系统的身份的过程。它确保请求访问的实体确实是他们声称的那样。此验证通常涉及提供凭据,例如用户名和密码,并将其与存储在安全位置的已知凭据集进行验证。身份验证机制通过确保只有授权用户才能访问受保护的资源来帮助保护系统和敏感信息。
我们将不再深入探讨此主题,因为读者已经熟悉其细节。
什么是 Blazor?
Blazor 是一个使用 C# 和 .NET 构建交互式 Web 应用程序的框架。它允许开发人员完全用 C# 创建 Web 应用程序,而无需依赖 JavaScript 来实现客户端功能(如 React 或 Angular)。 再次强调,我们将不再对此进行详细阐述,并鼓励读者探索专门的书籍或博客以获取更高级的概念。
信息
坦白说,我们不是 Blazor 专家。这里的重点仅在于探索该框架提供的身份验证功能。
搭建环境
现在,我们将继续在 Visual Studio 2022 IDE 中配置一个标准的 Blazor 环境。这个基础应用程序将作为我们逐步阐述底层概念的基础。
- 例如,创建一个名为EOCS.BlazorAuthentication的新解决方案,并在其中创建一个名为EOCS.BlazorAuthentication.Main的新 Blazor Web App 项目。

- 在添加信息时,请确保选择“无”作为身份验证类型,“服务器”作为交互式渲染模式,并勾选“包含示例页面”。

- 运行程序,验证可以访问所有路由而无需身份验证。
信息
在本系列中,我们使用的是 .NET 8 版本的 .NET 框架。
我们的目标现在是演示如何将身份验证无缝集成到此应用程序中,确保只有经过身份验证的用户才能访问其各个页面。
添加身份验证
引入身份验证需要 创建一个登录页面,用户可以在其中通过提供凭据来验证其身份。此外,它还涉及 指定其他页面除非提供的凭据经验证无误,否则无法访问。让我们详细探讨这两个方面。
创建登录页面
- 向应用程序添加一个名为Login.razor的新 Razor 组件,并在其中添加以下代码。
@page "/login"
<h3>Login</h3>
<form action="" method="post">
<button type="submit" class="btn btn-primary">Login</button>
</form>
- 运行程序。
我们可以清楚地看到,一个新页面已无缝集成到我们的应用程序中,可以通过 /login URL 访问。

指定其他页面无法访问
到目前为止,所有现有路由仍然公开可访问。现在我们将强制执行访问限制,以确保只有经过身份验证的用户才能查看我们的页面。
- 打开Routes.razor文件,向其添加[Authorize]属性,并使用以下代码编辑内容。
@inject NavigationManager navigationManager
<Router AppAssembly="@typeof(Program).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(Layout.MainLayout)">
<NotAuthorized>
@{
navigationManager.NavigateTo("/login", true);
}
</NotAuthorized>
</AuthorizeRouteView>
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
<NotFound>
@{
navigationManager.NavigateTo("/login", true);
}
</NotFound>
</Router>
信息
在框架的先前版本中,上述配置可能需要进行调整,尤其是在 Routes.razor 文件不存在的情况下。
建立此设置后,每个需要身份验证的文件都必须用 Authorize 属性进行标注。
@page "/"
@attribute [Authorize]
<PageTitle>Home</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
信息
为了确保 Authorize 属性被识别,请验证 _Imports.razor 文件中是否导入了正确的命名空间。
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Authorization
此代码中有几个值得注意的方面值得关注
-
需要仅供经过身份验证的用户访问的文件必须全局添加 [Authorize] 属性。这表示默认情况下启用身份验证,并且必须在页面加载时进行验证。
-
我们在Routes.cs类中使用了 AuthorizeRouteView 组件来定义请求页面时的行为:在我们的场景中,任何未经身份验证的用户都应重定向到登录页面,而经过身份验证的用户则被导向到请求的页面。
-
值得注意的是,如果恶意用户尝试访问一个不存在的 URL,他们也将被重定向到登录页面。
但是,运行程序时会出现错误。是什么原因导致了这种差异?

实际上,我们还没有告知 Blazor 必须在我们的应用程序中使用身份验证功能。
重要
我们必须承认,Microsoft 会随着框架的每个新版本修改 ASP.NET 应用程序的配置设置,这确实很麻烦。他们就不能保持 Program.cs 和 Startup.cs 类稳定吗?
本文档依赖于 .NET 8。
ASP.NET Core 中的身份验证是如何运作的?
ASP.NET Core 或 Blazor 中的身份验证无疑是一个复杂的主题,我们的目标是尽可能清晰地呈现它。此外,需要注意的是,根据解决方案启动时选择的配置,可能会出现某些差异。
ASP.NET 如何识别身份验证的必要性?
答案很简单:我们通过添加 [Authorize] 属性来指定需要身份验证的页面,并在Routes.cs类中指示某些路由应使用 AuthorizeRouteView 标签进行身份验证。
@page "/"
@attribute [Authorize]
<PageTitle>Home</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
<Router AppAssembly="@typeof(Program).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(Layout.MainLayout)">
<NotAuthorized>
@{
navigationManager.NavigateTo("/login", true);
}
</NotAuthorized>
</AuthorizeRouteView>
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
<NotFound>
@{
navigationManager.NavigateTo("/login", true);
}
</NotFound>
</Router>
这些配置将指示 ASP.NET 使用一种机制来通过身份验证过程验证当前用户的身份。但是,它会选择哪种过程?有许多可能性:用户可以使用 cookie、JWT 令牌、SAML 协议或其他方法进行身份验证。再次,答案很简单:ASP.NET 选择我们配置的方法!
那么,如何配置身份验证?
重要
一点术语:在 Microsoft 的行话中,ASP.NET Core 中的身份验证过程被称为身份验证方案。
因此,我们需要定义一个或多个身份验证方案(为用户提供多种登录选项是完全可以的)。好消息是,Microsoft 已经提供了原生方案,并简化了自定义方案的开发。
例如,在这里,我们配置我们的应用程序,要求在用户浏览器中存在一个名为“Auth”的 cookie 时进行身份验证。如果不存在此 cookie,用户将被重定向到登录页面。我们不需要编写复杂或晦涩的代码来获取和解密 cookie,因为 ASP.NET Core 原生地提供了此功能。

C# 中等效的代码如下(将其添加到 Program.cs 文件中)。
public static void Main(string[] args)
{
// ...
// Add authentication options
builder.Services.AddAuthentication("Auth")
.AddCookie("Auth", options =>
{
options.ExpireTimeSpan = TimeSpan.FromMinutes(20);
options.SlidingExpiration = true;
options.LoginPath = "/login";
});
builder.Services.AddCascadingAuthenticationState();
// ...
app.UseAuthentication();
app.UseAuthorization();
app.UseStaticFiles();
app.UseAntiforgery();
app.MapRazorComponents<App>().AddInteractiveServerRenderMode();
app.Run();
}
在此代码片段中请注意
- 我们定义了一个名为“Auth”的基于 cookie 的身份验证方案
- 我们将其指定为默认身份验证方案
如此配置后,ASP.NET 引擎就理解了身份验证是必需的,并且可以使用浏览器中的 cookie 来执行。

此时,当然,由于 cookie 不存在,我们将被重定向到登录页面。现在,我们需要建立一个创建它的方法。
完成身份验证过程
如何登录?
在这里,我们将创建一个极其简化的登录页面来验证用户。在实际场景中,我们需要从数据存储中检索数据来验证凭据的准确性。但是,为了说明目的,我们将直接创建所需的 cookie,而无需进行任何验证。
登录按钮触发 POST 方法,因此我们将创建一个控制器来响应此请求并在其中生成所需的 cookie。
-
在项目中添加一个新文件夹,命名为Authentication,并在其中添加一个名为AuthController的控制器。
-
在AuthController.cs中添加以下代码。
public class AuthController : Controller
{
[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> CookieLogin()
{
// Generate the claims
var claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.Name, "John Patton"));
claims.Add(new Claim(ClaimTypes.Role, "Contributor"));
var principal = new ClaimsPrincipal(new ClaimsIdentity(claims, "Auth"));
await HttpContext.SignInAsync("Auth", principal).ConfigureAwait(false);
return Redirect("/");
}
}
信息
HttpContext 的 SignInAsync 方法是一个原生方法,允许我们为 默认身份验证方案 登录一个主体。
在我们的例子中,由于身份验证方案依赖于 cookie,此方法将为我们处理 cookie 的创建,并考虑加密、过期等底层代码。
- 编辑Login.razor类。
@page "/login"
<form action="Auth/CookieLogin" method="post">
<button type="submit" class="btn btn-primary">Login</button>
</form>
- 同时编辑 Program.cs 类。
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddRazorComponents().AddInteractiveServerComponents();
// Add authentication options
builder.Services.AddAuthentication("Auth")
.AddCookie("Auth", options =>
{
options.ExpireTimeSpan = TimeSpan.FromMinutes(20);
options.SlidingExpiration = true;
options.LoginPath = "/login";
});
builder.Services.AddCascadingAuthenticationState();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
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.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseStaticFiles();
app.UseAntiforgery();
app.MapRazorComponents<App>().AddInteractiveServerRenderMode();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute("default", "{controller}/{action}");
});
app.Run();
}
现在,当我们单击登录时,会生成一个 cookie,我们可以在应用程序中导航。


如何注销?
类似地,我们可以通过调用 HttpContext 的 SignOutAsync 方法来实现注销过程。
public class AuthController : Controller
{
[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> CookieLogin()
{
//...
}
[HttpPost]
public async Task<IActionResult> CookieLogout()
{
await HttpContext.SignOutAsync("Auth").ConfigureAwait(false);
return Redirect("/login");
}
}
这里提出的逻辑相当基础,现在我们将深入探讨一个更复杂的主题:基于 SAML 协议的身份验证。但为了避免过度冗长,有兴趣进行此实现的读者可以在 此处 找到后续内容。