ASP.NET核心之路微服务第03部分:Identity

 

目录

介绍

文章系列

 安装ASP.NET Core Identity 

 配置ASP.NET Core Identity 

管理用户数据

使用Microsoft帐户,Google,Facebook等登录

结论


 下载Part03.zip

介绍

 

 

文章系列

  • ASP.NET核心之路微服务第01部分:构建视图
  • ASP.NET核心之路微服务第02部分:查看组件
  • ASP.NET核心之路微服务第03部分:ASP.NET核心身份
  • 04部分:SQLite
  • 05部分:Dapper
  • 06部分:SignalR
  • 07部分:单元测试Web API
  • 08部分:单元测试Web MVC应用程序
  • 09部分:监测健康检查
  • 10部分:Redis数据库
  • 11部分:IdentityServer4
  • 12部分:订购Web API
  • 13部分:Basket Web API
  • 14部分:Catalog Web API
  • 15部分:具有Polly的弹性HTTP客户端
  • 16部分:使用Swagger记录Web API
  • 17部分:Docker容器
  • 18部分:Docker配置
  • 19部分:使用Kibana进行中心日志

在这一系列文章的第2部分结尾处,我们有一个电子商务应用程序,其中包含用户可以从catalog中选择产品,将其放入购物篮并填写带有地址和其他个人数据的注册表单,用于将来的购物过程。当然,所有这些都是使用虚拟数据制作的。

我们的应用程序目前不需要用户登录或任何类型的密码。越来越多的电子商务网站也选择不要求此类信息,仅在结账页面要求客户的信用卡或其他付款方式。另一方面,许多电子商务网站需要登录和密码来验证用户。

两种模式都有优点和缺点。用用户体验的说法,一个不需要认证的电子商务网站对客户来说更方便,因为它减少了可能损害转换的摩擦。另一方面,身份验证使您能够识别用户并可能更好地分析他们的行为,并允许您为用户提供某些好处,例如显示先前在网站上购买的客户的订单历史记录。在本文中,我们将遵循第二种方法。

在本系列文章的第三部分中,我们将使用登录系统并确保只有经过身份验证的用户才能访问我们的应用程序。它允许您保护应用程序的敏感点免受匿名用户的攻击。通过身份验证,我们确保用户通过安全身份识别服务进入系统。这还使应用程序能够跟踪用户访问,识别使用模式,自动填写注册表单,查看客户订单历史记录和其他便利,从而增强用户体验。

如果您只需要一个包含登录名和密码列以及应用程序用户配置文件的用户表,那么ASP.NET Core Identity是您的最佳选择。

在本章中,我们将学习如何在我们的电子商务解决方案中安装ASP.NET Core Identity,并利用此框架提供的安全性,登录/注销,身份验证和用户配置文件功能。

默认情况下,Identity使用的数据库引擎是SQL Server。但是,我们将使用SQLite,这是一个比SQL Server更简单,更紧凑的数据库引擎。在安装Identity之前,我们将准备项目以使用这个新的数据库引擎。

右键单击MVC项目名称,选择Add NuGet Package子菜单,打开包安装页面,输入包名称: Microsoft.EntityFrameworkCore.SQLite

 

 

图片:项目上下文菜单

 

 

图片:添加Microsoft.EntityFrameworkCore.Sqlite

现在单击安装按钮并等待安装包。

好的,现在该项目已准备好接收ASP.NET Core Identity脚手架。

 安装ASP.NET Core Identity 

应用ASP.NET Core Identity脚手架

 

 

从头开始安装具有Identity的新ASP.NET Core与在现有项目中安装它不同。由于我们的项目没有Identity,我们将安装包含我们需要的功能的文件和程序集包。该过程类似于使用预制模块在建筑工地中建造墙壁。这个过程称为脚手架。

如果我们必须在我们的应用程序中手动创建登录/注销,身份验证和其他功能,则需要付出很多努力。我们正在讨论视图,业务逻辑,模型实体,数据访问,安全性等的开发,以及数小时的单元测试,功能测试,集成测试等。

幸运的是,我们的应用程序可以轻松地从身份验证和授权功能中受益。身份验证和授权在Web应用程序中无处不在。因此,Microsoft提供了一个可以透明地安装在缺少此类功能的ASP.NET Core项目中的软件包。它被称为ASP.NET Core Identity

要在我们的解决方案中应用ASP.NET Core Identity,我们右键单击该项目,单击Add Scaffolded Item,然后选择Add选项。这将打开一个新的Add Scaffold对话框窗口。

 

 

图片:项目上下文菜单

在这里,我们将选择  Installed> Identity> Identity

 

 

图片:添加脚手架对话框

ASP.NET Core Identity Scaffold将打开一个包含一系列配置参数的新对话窗口。在那里,您可以定义页面的布局,要包含的源代码,数据和用户上下文类,以及Identity将使用的数据库类型(SQL ServerSQLite)。

 

 

图片:添加标识对话框

我们选择以下选项:

  • Layout:我们项目中已存在的_Layout.cshtml文件。它将定义一个基本标记,由Identity页面和我们的应用程序的其余部分共享。
  • Identity pages:登录,注销,注册,外部登录。脚手架过程会将这些页面复制到我们的应用程序,您可以在其中编辑它们。请注意,您仍然可以导航到未标记的其他标识页,但您无法修改或自定义它们,因为它们不会出现在项目中。
  • Context classAppIdentityContext
  • User classAppIdentityUser。表示identity 系统中的用户

确认这些参数后,脚手架将修改我们的项目。最值得注意的变化是 我们项目的Areas / Identity文件夹下的新文件结构  

 

 

图片:Areas/Identity项目文件夹

观察Areas 文件夹下的新结构:

  • AppIdentityContext类:这是用于ASP.NET Core Identity的实体框架数据库上下文的类。
  • AppIdentityUser类:代表identity 系统中的用户。
  • Pages / Account下面的页面:这些是包含Identity 网页上的标记代码页。它们是Razor Pages,即一种MVC结构类型,其中视图位于文件中,控制器和模板的操作位于单个文件中。正如我们所说,这些页面可以在我们的应用程序中进行修改和自定义,但其他Identity 页面可以访问,但不能更改,因为它们的文件不在项目中。
  • 部分视图_ValidationScriptPartial_ViewImports_ViewStart
  • IdentityHostingStartup  类:ASP.NET Core WebHost在应用程序运行后立即执行此类。IdentityHostingStartup类配置Identity需要工作的数据库和其他服务。

创建和应用ASP.NET Core Identity 模型迁移

在我们的项目中安装ASP.NET Core Identity包是不够的。我们仍然需要生成数据库架构,其中包括ASP.NET Identity Core所需的表和初始数据。

当我们构建ASP.NET Identity Core的脚手架时,我们可以在IdentityHostingStartup.cs文件类中看到一个新的Identity数据模型自动添加到我们的项目中:

public void Configure(IWebHostBuilder builder)
{
    builder.ConfigureServices((context, services) => {
        services.AddDbContext<AppIdentityContext>(options =>
            options.UseSqlite(
                context.Configuration.GetConnectionString("AppIdentityContextConnection")));

        services.AddDefaultIdentity<AppIdentityUser>()
            .AddEntityFrameworkStores<AppIdentityContext>();
    });
}

清单:在IdentityHostingStartup.cs文件中配置Identity

注意上面的Entity Framework配置(AddDbContext方法)是如何使用AppIdentityContext类的,这是我们在脚手架过程中选择的名称。

同样的过程还为appsettings.json配置文件添加了一个新的AppIdentityContextConnection 连接字符串。ASP.NET Core Identity将使用此连接字符串来访问SQLite数据库:

.
.
.
"AllowedHosts": "*",
"ConnectionStrings": {
    "AppIdentityContextConnection": "DataSource=MVC.db"
}

清单:在appsettings.json上配置SQLite Connection 

但请注意,脚手架过程本身并没有创建Identity SQLite数据库。这可以通过创建新的实体框架迁移来实现。

要添加新迁移,请打开工具”>“程序包管理器控制台菜单,然后键入控制台。

PM> Add-Migration Identity

上面的命令添加了包含迁移语句的类,但它没有创建数据库本身:

 

 

图片:  迁移项目文件夹

要创建SQLite数据库,必须通过执行以下Update-Database命令来应用迁移:

PM> Update-Database -verbose

此命令创建在appsettings.json配置文件中包含的连接字符串中定义的MVC.db数据库文件:

 

 

图片:SQLite数据库文件 

现在让我们通过双击它来看看这个文件。这将打开我们在本文开头安装的SQLite应用程序的数据库浏览器:

 

 

图片:SQLite工具的数据库浏览器 

就这些了!现在,我们的应用程序已具备执行身份验证和授权所需的所有组件。从现在开始,我们将开始使用这些组件在我们的应用程序中集成ASP.NET Core Identity功能。

 配置ASP.NET Core Identity 

Identity 组件添加到后端

 

 

Identity组件已存在于我们的项目中。但是,我们需要添加进一步的配置,将这些组件与应用程序的其余部分集成。

在软件架构中,这被称为中间件

ASP.NET Core提供了一种标准方法,可将中间件集成到应用程序的正常执行中。这种机制类似于输水管道。每项新服务都进一步扩展了管道系统,将水带到一端,并将其传递到下一段。

 

 

图:ASP.NET Core Pipeline

同样,ASP.NET Core将沿着一系列中间件传递请求。收到请求后,每个中间件决定处理它或将请求传递给链中的下一个中间件。如果用户是匿名用户且资源需要授权,则Identity会将用户重定向到登录页面。

脚手架进程创建了IdentityHostingStartup类,该类已经配置了一些Identity服务。

public void Configure(IWebHostBuilder builder)
{
    ...
        services.AddDefaultIdentity<AppIdentityUser>()
            .AddEntityFrameworkStores<AppIdentityContext>();
    ...
}

清单:IdentityHostingStartup 

AddDefaultIdentity()方法向应用程序添加一组公共identity 服务,包括默认UI,令牌提供程序,以及配置身份验证以使用identity cookie

通过调用UseAuthentication()扩展方法启用Identity 。此方法将身份验证中间件添加到请求管道:

...
app.UseStaticFiles();
app.UseAuthentication();
...

清单:包括ASP.NET Core管道的Identity 

UseAuthentication()方法将身份验证中间件添加到指定的ApplicationBuilder,后者启用身份验证功能。

但是,上面的代码只配置了后端行为。对于前端,您可以通过在布局标记中包含允许用户登录或注册的部分视图,将ASP.NET Core Identity 视图与应用程序用户界面集成。我们来看下一节。

Identity 组件添加到前端

 

 

ASP.NET Core Identity脚手架过程包括在Views\Shared文件夹中的LoginPartial文件。此文件包含部分视图,该部分视图显示经过身份验证的用户的名称或登录和注册的超链接。

 

 

图片:_LoginPartial.cshtml部分视图

@using Microsoft.AspNetCore.Identity
@using MVC.Areas.Identity.Data
@inject SignInManager<AppIdentityUser> SignInManager
@inject UserManager<AppIdentityUser> UserManager

<ul class="navbar-nav">
@if (SignInManager.IsSignedIn(User))
{
    <li class="nav-item">
        <a id="manage" class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">Hello @UserManager.GetUserName(User)!</a>
    </li>
    <li class="nav-item">
        <form id="logoutForm" class="form-inline" asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="@Url.Action("Index", "Home", new { area = "" })">
            <button id="logout" type="submit" class="nav-link btn btn-link text-dark">Logout</button>
        </form>
    </li>
}
else
{
    <li class="nav-item">
        <a class="nav-link text-dark" id="register" asp-area="Identity" asp-page="/Account/Register">Register</a>
    </li>
    <li class="nav-item">
        <a class="nav-link text-dark" id="login" asp-area="Identity" asp-page="/Account/Login">Login</a>
    </li>
}
</ul>

清单:_LoginPartial.cshtml部分视图  

您可以将此组件添加到任何应用程序视图中,如下所示:

<partial name="_LoginPartial" />

但是,多次添加此行会导致不合需要的代码重复。我们可以通过在应用程序(_Layout.cshtml 文件)的标准布局视图中包含上面的行来避免这种冗余,因为这将导致组件通过我们所有的电子商务视图可见。我们需要将它更具体地包括在应用程序的导航栏中,在包含“navbar collapse”类的元素中:

<div class="navbar-collapse collapse justify-content-end">
    <partial name="_LoginPartial" />
    <ul class="nav navbar-nav">

清单:包含在Layout.cshtml页面中的_LoginPartial部分视图 

通过运行应用程序,我们现在可以在产品搜索页面的右上角看到注册和登录链接:

 

 

现在,我们将点击添加任何产品以导航到购物车页面。请注意登录和注册链接如何在此处出现:

 

 

Razor 页面

安装ASP.NET Core Identity脚手架时,项目中包含的新Identity组件不遵循MVC架构。相反,Identity组件基于Razor Pages

但是MVCRazor Pages之间的区别是什么?

我们可以从下面的屏幕截图中看到一个典型的MVC项目如何将一个页面的组件保存在一组文件中,这些文件分布在许多文件和文件夹中:

 

 

图:MVC项目结构 

所以,在MVC中没有一个网页文件。向那些对这项技术不熟悉的人解释这个事实有点尴尬。

如果您使用MVC应用程序,然后将视图称为页面(例如在Index.cshtml文件中),并且您不仅集中了模型数据,还集中了与该页面相关的服务器端代码(以前驻留在您的Controller上)专用于该页面的类(在Index.cshtml.cs文件中)——您现在称为页面模型?

如果您已经在本机移动应用程序中工作过,那么您可能已经在Model-View-ViewModelMVVM)模式中看到了类似的内容。

 

 

图片:Razor页面组件 

尽管与MVC不同,Razor Pages仍然依赖于ASP.NET Core MVC Framework。使用Razor Pages模板创建新项目后,Visual Studio通过Startup.cs文件配置应用程序以启用ASP.NET Core MVC框架,如我们刚才所见。

该模板不仅为MVC使用配置新的Web应用程序,还为示例应用程序创建Page文件夹和一组Razor页面和页面模型:

 

 

图片:Razor页面文件 

Razor的解剖

乍一看,Razor页面看起来非常像普通的ASP.NET MVC View文件。但Razor Page需要一个新的指令。每个Razor页面都必须以@page指令开头,该指令告诉ASP.NET Core将其视为Razor页面。下图显示了有关典型Razor页面的更多详细信息。

 

 

图片:Razor的解剖 

  • @page——将文件标识为Razor页面。没有它,ASP.NET Core无法访问该页面。 
  • @model——与MVC应用程序非常相似,它定义了从中生成绑定数据的类,以及页面请求的Get/Post方法。 
  • @using——定义命名空间的常规指令。 
  • @inject——配置应将哪个(些)接口实例注入页面模型类。 @ {}——Razor括号内的一段C#代码,在本例中用于定义页面标题。

创建新用户

由于我们创建了一个没有用户的新SQLite数据库,因此我们的客户需要填写IdentityRegister页面。以下是Layout.cshtml页面中的_LoginPartial.cshtml部分视图呈现的链接:

 

 

现在,让我们创建一个名为alice@smith.com的新客户帐户  

 

 

图片:注册页面 

当客户单击注册链接时,会将其重定向到/Identity/Account/Register 页面。我们可以看到,ASP.NET Core Identity已经为常见的用户注册问题提供了强大的解决方案。此外,ASP.NET Core Identity页面与我们的电子商务前端无缝集成。

 

 

图片:登录页面 

ASP.NET Core Identity还提供了需要付出很多努力才能实现的功能,例如忘记密码?” 和用户锁定(在多次输入错误密码后阻止用户登录时)。

@if (User.Identity.IsAuthenticated)
    {
        <ul class="nav navbar-nav">
            <li>
                <vc:notification-counter title="Notifications"...



                <vc:basket-counter title="Basket"...



            </li>
        </ul>
    }

清单:仅在用户通过身份验证时显示通知视图组件 

 

 

 

授权ASP.NET Core 资源

 

 

图片:匿名访问Basket 页面 

现在我们已经使用了Identity,我们将开始保护MVC项目的某些区域免受匿名访问,即未经身份验证的访问。这将确保只有输入有效登录名和密码的用户才能访问受保护的系统资源。但是应该保护哪些资源免受匿名访问?

控制器

它应该受到保护吗?

CatalogController

没有

BasketController

CheckoutController

NotificationsController

RegistrationController

请注意,CatalogController将不受保护。为什么?我们希望允许用户自由浏览网站的产品,而不必强迫他们使用密码登录。其他控制器都受到保护,因为它们涉及订单处理,这只能由客户完成。但是我们如何保护这些资源呢?我们必须使用授权属性标记这些控制器:

[Authorize]
public class BasketController : BaseController
{
    public IActionResult Index()
    ...
[Authorize]
public class BasketController : BaseController
...
[Authorize]
public class CheckoutController : BaseController
...
[Authorize]
public class NotificationsController : BaseController
...
[Authorize]
public class RegistrationController : BaseController
...

清单:定义控制器授权 

我们现在进行测试:当匿名用户试图访问其中一个标记为[Authorize]的功能时会发生什么?ASP.NET Core Identity将接收对应用程序进行的每个请求。如果用户已经过身份验证,Identity会将处理传递给管道的下一个组件。如果用户是匿名用户并且正在访问的资源需要授权,则Identity会将用户重定向到登录页面。

以匿名用户身份运行应用程序,我们转到产品搜索页面,我们可以毫无问题地访问该页面,因为此操作不受保护(即没有[Authorize]属性):

 

图片:匿名添加项目到购物篮  

ASP.NET Core尝试在Basket控制器中执行Index操作时, [Authorize] 属性会检查用户是否已通过身份验证。由于没有经过身份验证的用户,ASP.NET Core会通过URL重定向请求:

https://localhost:44340/Identity/Account/Login?ReturnUrl=%2FBasket

 

图片:登录页面 

请注意,此URL包含两部分:

我们可以通过打开开发人员工具(Chrome Key F12)并打开Headers选项卡来更仔细地查看此重定向过程,我们已经看到对Action/Cart操作的调用是通过HTTP代码302重定向的,这是代码重定向:

 

图片:登录重定向 

所以我们关闭了ASP.NET Core Identity 配置的主题。从现在开始,我们将开始获取最终可以在我们的应用程序中使用的用户信息。

管理用户数据

准备用户注册表

用户提交表单后,RegistrationViewModel必须准备好传输所有数据。

因此,我们将自定义用户信息添加到注册视图模型类。

public class RegistrationViewModel
{
    public string UserId { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public string Phone { get; set; }
    public string Address { get; set; }
    public string AdditionalAddress { get; set; }
    public string District { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string ZipCode { get; set; }
}

清单:/Models/ViewModels/RegistrationViewModel.cs中的自定义用户信息  

@using MVC.Models.ViewModels
@model RegistrationViewModel
@{
    ViewData["Title"] = "Registration";
}
<h3>Registration</h3>

<form method="post" asp-controller="checkout" asp-action="index">
    <input type="hidden" asp-for="@Model.UserId" />
    <div class="card">
        <div class="card-body">
            <div class="row">
                <div class="col-sm-4">
                    <div class="form-group">
                        <label class="control-label" for="name">Customer Name</label>
                        <input type="text" class="form-control" id="name" asp-for="@Model.Name" />
                        <span asp-validation-for="@Model.Name" class="text-danger"></span>
                    </div>
                    <div class="form-group">
                        <label class="control-label" for="email">Email</label>
                        <input type="email" class="form-control" id="email" asp-for="@Model.Email">
                        <span asp-validation-for="@Model.Email" class="text-danger"></span>
                    </div>
                    <div class="form-group">
                        <label class="control-label" for="phone">Phone</label>
                        <input type="text" class="form-control" id="phone" asp-for="@Model.Phone" />
                        <span asp-validation-for="@Model.Phone" class="text-danger"></span>
                    </div>
                </div>
                <div class="col-sm-4">
                    <div class="form-group">
                        <label class="control-label" for="address">Address</label>
                        <input type="text" class="form-control" id="address" asp-for="@Model.Address" />
                        <span asp-validation-for="@Model.Address" class="text-danger"></span>
                    </div>
                    <div class="form-group">
                        <label class="control-label" for="additionaladdress">Additional Address</label>
                        <input type="text" class="form-control" id="additionaladdress" asp-for="@Model.AdditionalAddress" />
                        <span asp-validation-for="@Model.AdditionalAddress" class="text-danger"></span>
                    </div>
                    <div class="form-group">
                        <label class="control-label" for="district">District</label>
                        <input type="text" class="form-control" id="district" asp-for="@Model.District" />
                        <span asp-validation-for="@Model.District" class="text-danger"></span>
                    </div>
                </div>
                <div class="col-sm-4">
                    <div class="form-group">
                        <label class="control-label" for="city">City</label>
                        <input type="text" class="form-control" id="city" asp-for="@Model.City" />
                        <span asp-validation-for="@Model.City" class="text-danger"></span>
                    </div>
                    <div class="form-group">
                        <label class="control-label" for="state">State</label>
                        <input type="text" class="form-control" id="state" asp-for="@Model.State" />
                        <span asp-validation-for="@Model.State" class="text-danger"></span>
                    </div>
                    <div class="form-group">
                        <label class="control-label" for="zipcode">Zip Code</label>
                        <input type="text" class="form-control" id="zipcode" asp-for="@Model.ZipCode" />
                        <span asp-validation-for="@Model.ZipCode" class="text-danger"></span>
                    </div>

                    <div class="form-group">
                        <a class="btn btn-success" href="/">
                            Keep buying
                        </a>
                    </div>
                    <div class="form-group">
                        <button type="submit"

                                class="btn btn-success button-notification">
                            Check out
                        </button>
                    </div>
                </div>
            </div>
        </div>
    </div>
</form>

清单:/MVC/Views/Registration/Index.cshtml上的客户注册视图

此外,新用户信息必须由AppIdentityUser 类持有。这不仅仅是一个普通的类。它定义了用于为User实体(表AspNetUsers)创建或修改数据库表的模型。  

public class AppIdentityUser : IdentityUser
{
    public string Name { get; set; }
    public string Phone { get; set; }
    public string Address { get; set; }
    public string AdditionalAddress { get; set; }
    public string District { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string ZipCode { get; set; }
}

清单:  /MVC/Areas/Identity/Data/AppIdentityUser.cs的自定义用户信息 

 

但请注意,我们必须再次创建一个新的Entity Framework Core Migration,以便将模型中的这些更改应用于数据库表。

要添加新迁移,请打开工具”>“程序包管理器控制台菜单,然后键入控制台。

PM> Add-Migration UserProfileData

上面的命令创建“UserProfileData”迁移,其中包含添加新用户表字段的迁移语句:

 

图片:迁移项目文件夹

要创建SQLite数据库,必须通过执行以下Update-Database命令来应用迁移:

PM> Update-Database -verbose

此命令将当前模型与数据库快照进行比较,并将差异应用于数据库中。在我们的示例中,检测到的差异是缺少的用户属性。

Identity 数据库中检索用户数据

 

图片:注册页面 

填写表单字段总是一项繁琐的工作。但有些情况下无法避免或推迟。想想电子商务网站上的客户信息:如果没有所有正确的数据,则无法计算货物并且无法处理购物情况。但是有一些方法可以减轻客户对此程序的不满。例如,您可以保存客户数据以用于将来的订单。但是我们如何使用ASP.NET Core Identity保存用户数据?

幸运的是,Identity提供了一个名为  UserManager<T>(其中T代表用户类或AppIdentityUser类)的类, 它提供用于管理持久性存储中的用户的API。也就是说,它公开了向数据库保存和检索用户数据所需的功能。

UserManager<T>参数通过依赖注入传递。但不要担心在Startup类上配置它。一旦添加了Identity脚手架,  IdentityHostingStartup类已经注册了依赖注入的UserManager<T> 类型。

RegistrationController中的Index方法中,我们可以看到GetUserAsync()方法如何用于从Identity存储(即SQLite数据库)异步检索当前用户。

[Authorize]
public class RegistrationController : BaseController
{
    private readonly UserManager userManager;

    public RegistrationController(UserManager<AppIdentityUser> userManager)
    {
        this.userManager = userManager;
    }

    public async Task<IActionResult> Index()
    {
        var user = await userManager.GetUserAsync(this.User);
        var viewModel = new RegistrationViewModel(
            user.Id, user.Name, user.Email, user.Phone,
            user.Address, user.AdditionalAddress, user.District,
            user.City, user.State, user.ZipCode
        );
        return View(viewModel);
    }
}

清单:/MVC/Controllers/RegistrationController.cs文件

之后,我们使用从AspNetUsers表中检索的用户数据填充RegistrationViewModel类。反过来,视图模型被传递到视图中以自动填充第二次客户的注册表单,我们可以在下面的<input>字段中看到。

<input class="form-control" asp-for="@Model.Phone" />
...
<input class="form-control" asp-for="@Model.Address" />
...
<input class="form-control" asp-for="@Model.AdditionalAddress" />
...
<input class="form-control" asp-for="@Model.District" />
...
<input class="form-control" asp-for="@Model.City" />
...
<input class="form-control" asp-for="@Model.State" />
...
<input class="form-control" asp-for="@Model.ZipCode" />
...

清单:/MVC/Views/Registration/Index.cshtml中的新标记助手

将用户数据保留到Identity 数据库

下面的代码显示了如何操作 

  • 检查模型是否有效,即是否满足RegistrationViewModel类验证规则。
  • 使用GetUserAsync()方法异步检索用户 
  • 通过应用表单数据修改来自数据库的用户对象
  • 使用UpdateAsync()方法更新SQLite数据库 
  • 如果模型无效,则重定向回注册视图
[Authorize]
public class CheckoutController : BaseController
{
    private readonly UserManager<AppIdentityUser> userManager;

    public CheckoutController(UserManager<AppIdentityUser> userManager)
    {
        this.userManager = userManager;
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Index(RegistrationViewModel registration)
    {
        if (ModelState.IsValid)
        {
            var user = await userManager.GetUserAsync(this.User);

            user.Email = registration.Email;
            user.Phone = registration.Phone;
            user.Name = registration.Name;
            user.Address = registration.Address;
            user.AdditionalAddress = registration.AdditionalAddress;
            user.District = registration.District;
            user.City = registration.City;
            user.State = registration.State;
            user.ZipCode = registration.ZipCode;

            await userManager.UpdateAsync(user);
            return View(registration);
        }
        return RedirectToAction("Index", "Registration");
    }
}

清单:Part 03 /MVC/Controllers/CheckoutController.cs文件

下订单时,Checkout视图会显示订单确认,并显示网站上的谢谢消息。我们再一次为视图绑定使用RegistrationViewModel作为源。

@model RegistrationViewModel
...
<p>Thank you very much, <b>@Model.Name</b>!</p>
<p>Your order has been placed.</p>
<p>Soon you will receive an e-mail at <b>@Model.Email</b> including all order details.</p>

清单:/MVC/Views/Checkout/Index.cshtml中的绑定结账数据

请注意在更新数据库用户信息之前,CheckoutController类如何检查模型是否有效:

public async Task<IActionResult> Index(RegistrationViewModel registration)
{ 
   if (ModelState.IsValid) 
   {

服务器端需要进行此验证,以便我们不会使用不一致的信息更新数据库表。但这只是我们防护的最后一道防线。您永远不应该唯一依赖服务器端检查来进行数据验证。还有什么必须做的?您还应该进行早期验证,在用户尝试提交订单时执行客户端检查。幸运的是,ASPNET Core项目提供了客户端验证的部分视图:

<environment include="Development">
    <script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
    <script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
</environment>
<environment exclude="Development">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.17.0/jquery.validate.min.js"

            asp-fallback-src="~/lib/jquery-validation/dist/jquery.validate.min.js"

            asp-fallback-test="window.jQuery && window.jQuery.validator"

            crossorigin="anonymous"

            integrity="sha256-F6h55Qw6sweK+t7SiOJX+2bpSAa3b/fnlrVCJvmEj1A=">
    </script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-unobtrusive/3.2.11/jquery.validate.unobtrusive.min.js"

            asp-fallback-src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"

            asp-fallback-test="window.jQuery && window.jQuery.validator && window.jQuery.validator.unobtrusive"

            crossorigin="anonymous"

            integrity="sha256-9GycpJnliUjJDVDqP0UEu/bsm9U+3dnQUH8+3W10vkY=">
    </script>
</environment>

清单:_ValidationScriptsPartial.cshtml部分视图

验证部分视图必须使用<partial>标记帮助程序包含在注册视图中:

@section Scripts
{
    <partial name="~/Views/Shared/_ValidationScriptsPartial.cshtml"/>
}

清单:包含在\MVC\Views\Registration\Index.cshtml中的表单客户端验证脚本

使用Microsoft帐户,GoogleFacebook等登录

为什么允许外部帐户登录?

Web应用程序中注册新用户和密码通常是一个繁琐的过程,在电子商务应用程序中不仅令客户失望,而且对业务也有害,因为任何额外的步骤都可能阻碍潜在的购买者。他们可能更喜欢另一个更友好,更少官僚主义的电子商务网站。简而言之:强迫用户注册可能会损害销售。

外部登录过程允许我们将identity过程与外部服务(如MicrosoftGoogleFacebook)上的现有帐户集成,而无需创建新密码,可以为我们的客户提供更方便的注册过程。

但是,必须将此外部登录过程作为替代方法实现,而不是唯一的注册方法。

幸运的是,ASP.NET Core Identity提供了一种机制,允许用户通过外部提供程序(如MicrosoftGoogleFacebookTwitter等)执行登录。

使用Microsoft帐户配置外部登录

请记住,外部登录服务不了解您的应用程序,反之亦然。双方都需要一个配置来定义认证过程中涉及哪些应用程序/服务。

让我们创建所需的配置,以便我们的用户可以使用他们的Microsoft帐户(@ hotmail.com@ outlook.com等)作为我们的应用程序的登录方式。

首先,Microsoft身份验证服务需要知道我们的应用程序。我们需要输入名为Microsoft Application Registration Portal https://apps.dev.microsoft.com的服务地址。并为我们的应用程序创建设置。

首先,您(开发人员)需要使用您的Microsoft帐户登录才能访问门户网站:

 

图:微软的登录提供商页面 

在此开发人员门户中,您可以看到已注册的应用程序。如果您仍然没有Microsoft帐户,则可以创建一个。登录后,您将被重定向到我的应用页面: 

 

图:Microsoft Developer Portal上的我的应用程序 

在这里,您将注册一个新的应用程序。选择右上角的添加应用程序,然后输入应用程序的名称。

让我们给它一个有意义的名字,比如GroceryStore

 

图片:注册新的应用程序 

单击创建应用程序以继续注册页面。提供名称并记下应用程序标识的值,稍后您可以将其用作ClientId

 

图片:生成新的应用密码 

 

图片:定义应用程序平台 

在此处,您将单击平台部分中的添加平台,然后选择“Web平台

 

图:可用的App平台 

Web平台下,输入您的开发URL,将/ signin-microsoft添加到重定向字段URL(例如: https://localhost:44320/signin-microsoft)。我们稍后将配置的Microsoft身份验证方案将自动处理/ signin-microsoft路由中的请求以实现OAuth流:

 

图片:配置应用程序的重定向URL 

请注意,在此页面上,我们将单击添加URL”以确保已添加URL

 

如有必要,请填写任何其他应用程序设置,然后单击页面底部的保存以保存对应用程序配置的更改。

 

现在,查看注册页面上显示的Application Id。单击以在应用程序机密部分中生成新密码。这将显示一个框,您可以在其中复制应用程序密码:

 

图片:获取密码 

我们在哪里存储此密码?在现实世界的商业应用程序中,我们必须使用某种形式的安全存储,例如环境变量或秘密管理器工具(https://docs.microsoft.com/aspnet/core/security/app-secrets?view = aspnetcore-2.2tabs = windows

但是,为了让我们的生活更轻松,我们只需使用appsettings.json配置文件来存储在Microsoft Developer Portal上注册的应用程序密码。在这里,我们创建了两个新的配置键:

  • ExternalLogin:Microsoft:ClientId:在Microsoft创建的Web应用程序ID
  • ExternalLogin:Microsoft:ClientSecret:在Microsoft中创建的Web应用程序的密码
"ExternalLogin": {
  "Microsoft": {
    "ClientId": "nononononononononononnnononoon",
    "ClientSecret": "nononononononononononnnononoon"
  }
}

清单:appsettings.json上的外部登录配置

现在让我们将以下摘录添加到Startup类的CofigureServices方法中,以通过Microsoft帐户启用身份验证:

services.AddAuthentication()
    .AddMicrosoftAccount(options =>
    {
        options.ClientId = Configuration["ExternalLogin:Microsoft:ClientId"];
        options.ClientSecret = Configuration["ExternalLogin:Microsoft:ClientSecret"];
    });

清单:Startup类的外部登录配置

再次运行我们的电子商务应用程序,我们可以在登录页面上看到一个右侧面板,您可以在其中找到一个允许您使用Microsoft外部提供程序登录的新按钮。

 

图:新的Microsoft外部登录选项  

登录到Microsoft页面后,我们的用户将被重定向到ASP.NET Core Identity提供的帐户关联页面。在这里,我们将点击注册以完成Microsoft帐户与我们的电子商务帐户之间的关联:

 

图片:关联帐户 

正如我们在下面看到的,客户端现在已在Microsoft的电子邮件中注册,我们的应用程序无需进一步的登录信息! 

 

图片:使用Microsoft帐户登录 

请注意,Identity直接创建的帐户可以与在GoogleMicrosoftFacebook等外部机制中创建的用户帐户并存,我们可以在SQLite数据库MVC.db的用户表(AspNetUsers)中看到: 

图片:AspNetUsers SQLite 

最后,我们可以调查在我们的系统之外创建了哪些用户帐户。只需从MVC.db数据库中打开AspNetUserLogins表: 

 

图片:AspNetUserLogins SQLite 

使用Google帐户配置外部登录

现在让我们创建所需的配置,以便我们的用户可以使用Google帐户(@ gmail.com)作为我们应用程序的替代登录方式。

Google身份验证服务还需要了解我们的应用程序。首先我们需要去谷歌帐户即可访问网站 。在该页面,您必须单击以配置项目:

 

 图片:将Google登录集成到您的网络应用中

现在,您必须为Google登录配置项目。在此输入项目的名称。让我们给它一个有意义的名字,比如GroceryStore

 

图片:为Google登录配置项目

现在是时候配置您的OAuth客户端了。在用户使用自己的Google帐户登录时,键入要向用户显示的应用的友好名称:

 

图片:配置您的OAuth客户端 

接下来,您告诉谷歌您的应用所在的位置。在这种情况下,我们使用Web服务器,因为它是我们的ASP.NET Core Web应用程序,它将调用Google外部登录提供程序进行用户身份验证。

 

图片:你在哪里调用? 

Google还需要我们的应用重定向URI。只要我们的用户通过Google进行身份验证,就会使用授权码调用http://localhost:5001/signin-google URI进行访问。 

 

图片:授权重定向URI 

单击创建按钮后,您可以检查需要在应用程序中使用的新创建的客户端ID和客户端密钥值。

现在,让我们打开appsettings.json文件并插入以下键和值:

  • ExternalLogin:Google:ClientId:在Google上创建的Web应用程序ID

  • ExternalLogin:Google:ClientSecret:在Google中创建的Web应用程序的密码

"ExternalLogin": {
  "Microsoft": {
    "ClientId": "nononononononononononnnononoon",
    "ClientSecret": "nononononononononononnnononoon"
  },
  "Google": {
    "ClientId": "nononononononononononnnononoon",
    "ClientSecret": "nononononononononononnnononoon"
  }
}

清单:appsettings.json上的外部登录配置

现在让我们将以下摘录添加到Startup类的CofigureServices方法中,以通过Google帐户启用身份验证:

services.AddAuthentication()
    .AddMicrosoftAccount(options =>
    {
        options.ClientId = Configuration["ExternalLogin:Microsoft:ClientId"];
        options.ClientSecret = Configuration["ExternalLogin:Microsoft:ClientSecret"];
    })
    .AddGoogle(options =>
    {
        options.ClientId = Configuration["ExternalLogin:Google:ClientId"];
        options.ClientSecret = Configuration["ExternalLogin:Google:ClientSecret"];
    });

清单:Startup类的外部登录配置

再次运行我们的电子商务应用程序,我们可以在登录页面上看到一个右侧面板,您可以在其中找到一个允许您使用Google外部提供商登录的新按钮。

 

图片:新的Google外部登录选项  

登录到Microsoft页面后,我们的用户将被重定向到ASP.NET Core Identity提供的帐户关联页面。在这里,我们将点击注册以完成Google帐户与我们的电子商务帐户之间的关联:

 

图片:关联帐户 

如下所示,客户现在已在Google的电子邮件中注册,我们的应用程序无需再提供登录信息! 

 

图片:选择Google帐户  

 

使用Google帐户配置外部登录

最后,让我们将AddGoogle()扩展方法的调用添加到Startup类的CofigureServices方法,以通过Google帐户启用身份验证:

services.AddAuthentication()
    .AddMicrosoftAccount(options =>
    {
        options.ClientId = Configuration["ExternalLogin:Microsoft:ClientId"];
        options.ClientSecret = Configuration["ExternalLogin:Microsoft:ClientSecret"];
    })
    .AddGoogle(options =>
    {
        options.ClientId = Configuration["ExternalLogin:Google:ClientId"];
        options.ClientSecret = Configuration["ExternalLogin:Google:ClientSecret"];
    });

清单:在Startup类中添加外部Google登录提供程序

结论

我们完成了“ASP.NET核心之路微服务第3部分。在本文中,我们了解了我们在上一课程结束时所使用的应用程序的用户身份验证需求。然后,我们决定使用ASP.NET Core Identity作为我们的电子商务Web应用程序的身份验证解决方案。

我们已经学习了如何使用ASP.NET Core Identity 标识创建过程来授权MVC应用程序,以便它可以利用用户身份验证,资源保护和敏感页面(如BasketRegistrationCheckout)的优势。

我们了解用户布局流如何工作,并且我们已经学习如何配置MVC Web应用程序以满足该流的要求。一旦理解了登录和注销过程,我们就开始修改我们的MVC应用程序以使用id和用户名,以及每个登录用户的其他注册信息,这些信息在我们的MVC应用程序的上下文中表示正在进行购买的客户。

最后,我们学习了如何执行外部登录过程,这使我们能够将identity 过程与外部服务(如MicrosoftGoogleFacebook)中的现有帐户集成,从而为我们的客户提供更方便的注册流程。

 

原文地址:https://www.codeproject.com/Articles/5129264/ASP-NET-Core-Road-to-Microservices-Part-03-Identit

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值