定制ASP.NET Core的身份认证

大家好,我是张飞洪,感谢您的阅读,我会不定期和你分享学习心得,希望我的文章能成为你成长路上的垫脚石,让我们一起精进。

在本章,我们将学习如何定制ASP.NET Core认证机制。微软把安全认证当做ASP.NET Core框架的一部分,足以看见它是应用程序非常最重要的内容。在文将介绍如何定制ASP.NET Core认证UI的基本实现,以及如何向IdentityUser添加自定义信息。我们将介绍以下几点:

  • 介绍ASP.NET Core身份认证
  • 自定义IdentityUser
  • 自定义Identity视图(views)
  • 该主题所属ASP.NET Core架构的MVC层:

技术准备

创建一个ASP.NET Core应用程序并用VS Code打开:

dotnet new mvc -n AuthSample -o AuthSample --auth Individual
cd AuthSample 
code .

ASP.NET Core身份认证介绍

用户身份是表示用户对象,也可以是用户组。通过它我们可以了解用户及其所属的权限。可以给身份或者叫用户分配包含权限的角色。例如,可以给一个名为仓库管理员的角色只分配写入的权限。用户身份也可以嵌套。一个用户可以是一个组的一部分,一个组可以是另一个组的一部分,依此类推。

ASP.NET Core Identity是一个框架,它在.NET中用来存储和读取用户信息。该框架还提供了添加登录表单、注册表单、会话处理等机制。它还提供以加密和安全的方式存储凭据。ASP.NET Core Identity还提供了多种验证用户身份的方法:

  • Individual(个人):应用程序自行管理身份。它有一个存储用户信息的数据库,并自行管理登录、注销、注册等。
  • IndividualB2C:自行管理用户数据,但从Azure B2C获取数据。
  • SingleOrg:身份由Azure Active Directory(AD)管理;登录、注销等等都是由Azure AD完成的。应用程序只需从web服务器获取一个随时可用的身份。
  • 多组织:与上一个相同,但为多个Azure AD组织启用。
  • Windows:经典的Windows身份验证,仅当应用程序由IIS托管时才可用。用户还可以从web服务器获得一个随时可用的身份。
  • 本文不是关于不同的身份验证方法,因为这个主题太大了。

上面命令行里的–auth标志设置为Individual,用于启用个人身份验证,并创建ASP.NET Core MVC应用程序。这意味着它附带了一个数据库来存储用户。–auth标志会添加所有相关的代码和依赖项。

–auth标志创建了一个名为Identity的区域,其中包含_ViewStart.cshtml文件,它引用新项目的_Layout.cshtml文件。实际的登录或注册界面在引用此项目的类库中提供。

该案例项目包含一个Data文件夹,其中包含EF Core的DbContext,以及用于创建和更新的数据库迁移。

除了Program.cs外,所有其他部分常规MVC应用完全相同。

如果您使用.NET CLI创建了应用程序,默认则会使用SQLite数据库。如果使用Visual Studio创建此应用程序,则会使用SQL Server存储用户数据。

在启动应用程序之前,在终端中调用以下命令创建和更新数据库:

dotnet ef database update

如果不起作用,您可能需要首先在.NET CLI中安装实体框架工具:

dotnet tool install -g dotnet-ef

然后执行:
dotnet watch
应用程序现在将在监视模式下启动,并启用热重新加载。它还将打开浏览器窗口并调用应用程序:

如您所见,右上方有一个菜单,其中包含此应用程序的“注册”和“登录”选项。单击登录链接可进入以下登录屏幕:

如上所述,该视图来自一个已编译的Razor库,它为Identity区域提供了必要的视图。我们会自动从框架中获取此UI。

最后,我们快速了解一下Program.cs,这也与我们在上一章中看到的文件不同。

在注册服务的上部,有几行代码用于注册DbContext以及数据库异常页:

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlite(connectionString)); 
builder.Services.AddDatabaseDeveloperPageExceptionFilter(); 
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)  .AddEntityFrameworkStores<ApplicationDbContext>();

还有添加身份认证的EntityFramework Core的注册。它被配置为只允许确认的帐户,这意味着:作为用户需要确认电子邮件才能登录。
在中间件的下部,我们看到使用了身份验证和授权:

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

身份验证通过读取身份验证cookie来识别用户,它还将所有相关信息添加到Identity对象。
我们可能还需要通过向用户添加更多属性来扩展用户配置文件。我们在下面介绍如何做到这一点。

自定义IdentityUser

IdentityUser具有以下字段:Id、用户名、密码、电子邮件和电话号码。
由于显示名称可能与用户名不同,我们会添加Name属性,另外,假设我们想向用户发送生日祝福,我们应该如何扩展他们的出生日期呢?
为此,需要添加一个名为WebAppUser.cs的文件到Data文件夹中:

using Microsoft.AspNetCore.Identity; 
namespace AuthSample.Data; 
public class WebAppUser : IdentityUser {     
  [PersonalData]     
  public string? Name { get; set; }     
  [PersonalData]     
  public DateTime DOB { get; set; } 
}

如上所示,WebAppUser.cs派生自IdentityUser,并扩展两个属性。
在Program.cs,我们需要修改服务注册以使用新的WebAppUser:

builder.Services.AddDefaultIdentity<WebAppUser>
我们还需要更改DbContext,使用WebAppUser:

public class ApplicationDbContext :  IdentityDbContext<WebAppUser, IdentityRole, string>

您还需要将using语句添加到Microsoft.AspNetCore.Identity中。这是第一步。我们现在需要更新数据库:

dotnet ef migrations add CustomUserData 
dotnet ef database update

一旦使用自定义属性扩展了IdentityUser,就可以在在ASP.NET Core Identity U的用户配置文件中使用它。

自定义Identity视图(views)

ASP.NET Core Identity视图来自编译的Razor库,我们应该如何自定义呢?
我们只需要在Area内的预定义文件夹结构中用自定义视图覆盖给定视图即可。前提是我们需要先创建一个自定义视图,添加扩展字段或更改布局。
如前所述,项目中已经有一个名为Identity的Area,内有一个Pages文件夹。在这里,我们需要创建一个名为Account的新文件夹,然后放置一个名为“注册”的新Register.cshtml页面在这个文件夹中,并将以下内容放在里面,以查看视图的覆盖是否有效:
@page @{ } <h1>Hello Register Form</h1>
运行应用程序并单击左上角的注册,您将看到以下页面:

实际上,我们不需要自己覆盖视图。有一个代码生成器可用于构建您想要覆盖的视图。
通过调用以下命令安装代码生成器:
dotnet tool install -g dotnet-aspnet-codegenerator
如果尚未完成,您还需要在项目中安装以下软件包:

dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design 
dotnet add package Microsoft.EntityFrameworkCore.Design 
dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore 
dotnet add package Microsoft.AspNetCore.Identity.UI 
dotnet add package Microsoft.EntityFrameworkCore.SqlServer 
dotnet add package Microsoft.EntityFrameworkCore.Tools

要了解代码生成器可以做什么,请运行以下命令:
dotnet aspnet-codegenerator identity -h
第一个更改是让用户在注册页面上填写name属性,我们先构建注册页面:

dotnet aspnet-codegenerator identity -dc AuthSample.Data.ApplicationDbContext --files "Account.Register" -sqlite

此命令告诉代码生成器使用现有的ApplicationDbContext和Sqlite。如果不指定此项,它将创建一个新的DbContext或注册现有的DbContext,以便与SQLServer而不是SQLite一起使用。

如果一切都OK了,代码生成器应该只添加Register.cshtml页面以及一些基础结构文件:

代码生成器还会知道项目正在使用自定义WebAppUser而不是IdentityUser,这意味着在生成的代码中使用了WebAppUser。
现在,我们更改一下Register.cshtml,将显示名称添加到表单中。在第15行电子邮件字段的表单元素之前添加以下行:

<div class="form-floating">     
<input asp-for="Input.Name" class="form-control" autocomplete="name" aria-required="true" />
<label asp-for="Input.Name"></label> 
<span asp-validation-for="Input.Name"  class="text-danger"></span> 
</div>

此外,Regiser.cshtml.cs需要更改。ImportModel类需要Name属性:

public class InputModel {     
[Required]     
[Display(Name = "Display name")]     
public string Name { get; set; }

在PostAsync方法中,将Name属性分配给新创建的用户:

var user = CreateUser(); 
user.Name = Input.Name;

启动申请后,您将看到以下注册表格:

由于用户可能需要更新名称,我们还需要更改配置文件页面上的视图。这里还需要添加出生日期:

dotnet aspnet-codegenerator identity -dc AuthSample.Data.ApplicationDbContext --files "Account.Manage.Index" -sqlite

打开新创建的Index.chtml.cs,并将以下属性放在InputModel类中:

public class InputModel {     
  [Required]     
  [Display(Name = "Display name")]     
  public string Name { get; set; }     
  [Display(Name = "Date of birth")]     
  public DateTime DOB { get; set; }
}

现在可以在相应的Index.chtml中使用这些属性。下面代码片段需要放在验证和用户名之间:

<div class="form-floating">     
<input asp-for="Input.Name" class="form-control"  autocomplete="name" aria-required="true" />     
<label asp-for="Input.Name"></label>     
<span asp-validation-for="Input.Name" class="text-danger">
</span> 
</div> 
<div class="form-floating">     
<input asp-for="Input.DOB" class="form-control"  type="date"/>     
<label asp-for="Input.DOB" class="form-label"></label> 
</div>

最后,还需要一些更改才能用保存的数据填充表单。在LoadAsync方法中,需要使用新属性扩展InputModel的实例化:

Input = new InputModel {     
PhoneNumber = phoneNumber,     
Name = user.Name,     
DOB = user.DOB 
};

用户保存表单时,还需要保存更改的值。将下一个代码段放在OnPostAsync方法中:

user.Name = Input.Name; 
user.DOB = Input.DOB; 
await _userManager.UpdateAsync(user);

将InputModel的值设置为WebAppUser属性,并将更改保存在数据库中。
我们在终端中调用dotnet watch来尝试一下。
Profile页面现在看起来类似于:

您现在可以更改显示名称并添加出生日期。如果用户填写了显示名称,他们可能会在登录后在左上角显示。
打开Views/Shared文件夹的_LoginPartial.cshtml,并将前四行替换为以下代码段:

@using Microsoft.AspNetCore.Identity 
@using AuthSample.Data 
@inject SignInManager<WebAppUser> SignInManager 
@inject UserManager<WebAppUser> UserManager 
@{  var user = await @UserManager.GetUserAsync(User); }

做using部分,将SignInManager和UserManager的泛型参数从IdentityUser类型更改为WebAppUser。
在代码块部分,通过传入当前用户,通过UserManager加载当前WebAppUser。

现在,需要更改用户名的输出以写入显示名称:
Hello @user?.Name!

当dotnet watch仍在运行时,浏览器中运行的应用程序应该已经更新。也许你需要重新登录。现在,您应该会在右上角看到显示名称:

总结

在本章中,我们学习了如何扩展ASP.NET Core Identity,通过添加其他属性来增强用户对象。我们还学习了如何增强Identity UI以加载、保存和更新新用户属性的值。

但是,应该如何管理应用程序用户的角色?我们将在下一章中学习的关于配置身份管理的内容。

Tags: