清单:包含在 Layout.cshtml 页面中的 _LoginPartial 部分视图
通过运行应用程序,我们现在可以在产品搜索页面的右上角看到注册和登录链接:
现在,我们将点击添加任何产品以导航到购物车页面。请注意登录和注册链接如何在此处出现:
Razor 页面
安装 ASP.NET Core Identity 脚手架时,项目中包含的新 Identity 组件不遵循 MVC 架构。相反, Identity 组件基于 Razor Pages 。
但是 MVC 和 Razor Pages 之间的区别是什么?
我们可以从下面的屏幕截图中看到一个典型的 MVC 项目如何将一个页面的组件保存在一组文件中,这些文件分布在许多文件和文件夹中:
图: MVC 项目结构
所以,在 MVC 中没有一个 “ 网页 ” 文件。向那些对这项技术不熟悉的人解释这个事实有点尴尬。
如果您使用 MVC 应用程序,然后将视图称为 “ 页面 ” (例如在 Index.cshtml 文件中),并且您不仅集中了模型数据,还集中了与该页面相关的服务器端代码(以前驻留在您的 Controller 上)专用于该页面的类(在 Index.cshtml.cs 文件中)——您现在称为页面模型?
如果您已经在本机移动应用程序中工作过,那么您可能已经在 Model-View-ViewModel ( MVVM )模式中看到了类似的内容。
图片: 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 数据库,因此我们的客户需要填写 Identity 的 Register 页面。以下是 Layout.cshtml 页面中的 _LoginPartial.cshtml 部分视图呈现的链接:
现在,让我们创建一个名为 [email protected] 的新客户帐户 。
图片:注册页面
当客户单击 “ 注册 ” 链接时,会将其重定向到 /Identity/Account/Register 页面。我们可以看到, ASP.NET Core Identity 已经为常见的用户注册问题提供了强大的解决方案。此外, ASP.NET Core Identity 页面与我们的电子商务前端无缝集成。
图片:登录页面
ASP.NET Core Identity 还提供了需要付出很多努力才能实现的功能,例如 “ 忘记密码? ” 和用户锁定(在多次输入错误密码后阻止用户登录时)。
@if (User.Identity.IsAuthenticated)
{
}
清单:仅在用户通过身份验证时显示通知视图组件
授权 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 包含两部分:
用户必须进行身份验证的 URL : http://localhost:5001/Identity/Account/Login
用户将在身份验证后返回的原始 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";
}
Registration
清单: /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 代表用户类或 AppIdentityUser 类)的类, 它提供用于管理持久性存储中的用户的 API 。也就是说,它公开了向数据库保存和检索用户数据所需的功能。
UserManager 参数通过依赖注入传递。但不要担心在 Startup 类上配置它。一旦添加了 Identity 脚手架, IdentityHostingStartup 类已经注册了依赖注入的 UserManager 类型。
在 RegistrationController 中的 Index 方法中,我们可以看到 GetUserAsync() 方法如何用于从 Identity 存储(即 SQLite 数据库)异步检索当前用户。
[Authorize]
public class RegistrationController : BaseController
{
private readonly UserManager userManager;
public RegistrationController(UserManager userManager)
{
this.userManager = userManager;
}
public async Task 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 类。反过来,视图模型被传递到视图中以自动填充第二次客户的注册表单,我们可以在下面的 字段中看到。
...
...
...
...
...
...
...
清单: /MVC/Views/Registration/Index.cshtml 中的新标记助手
将用户数据保留到 Identity 数据库
下面的代码显示了如何操作
检查模型是否有效,即是否满足 RegistrationViewModel 类验证规则。
使用 GetUserAsync() 方法异步检索用户
通过应用表单数据修改来自数据库的用户对象
使用 UpdateAsync() 方法更新 SQLite 数据库
如果模型无效,则重定向回注册视图
[Authorize]
public class CheckoutController : BaseController
{
private readonly UserManager userManager;
public CheckoutController(UserManager userManager)
{
this.userManager = userManager;
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task 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
...
Thank you very much, @Model.Name !
Your order has been placed.
Soon you will receive an e-mail at @Model.Email including all order details.
清单: /MVC/Views/Checkout/Index.cshtml 中的绑定结账数据
请注意在更新数据库用户信息之前, CheckoutController 类如何检查模型是否有效:
public async Task Index(RegistrationViewModel registration)
{
if (ModelState.IsValid)
{
服务器端需要进行此验证,以便我们不会使用不一致的信息更新数据库表。但这只是我们防护的最后一道防线。您永远不应该唯一依赖服务器端检查来进行数据验证。还有什么必须做的?您还应该进行早期验证,在用户尝试提交订单时执行客户端检查。幸运的是, ASPNET Core 项目提供了客户端验证的部分视图:
清单: _ValidationScriptsPartial.cshtml 部分视图
验证部分视图必须使用 标记帮助程序包含在注册视图中:
@section Scripts
{
}
清单:包含在 \MVC\Views\Registration\Index.cshtml 中的表单客户端验证脚本
使用 Microsoft 帐户, Google , Facebook 等登录
为什么允许外部帐户登录?
在 Web 应用程序中注册新用户和密码通常是一个繁琐的过程,在电子商务应用程序中不仅令客户失望,而且对业务也有害,因为任何额外的步骤都可能阻碍潜在的购买者。他们可能更喜欢另一个更友好,更少官僚主义的电子商务网站。简而言之:强迫用户注册可能会损害销售。
外部登录过程允许我们将 identity 过程与外部服务(如 Microsoft , Google 和 Facebook )上的现有帐户集成,而无需创建新密码,可以为我们的客户提供更方便的注册过程。
但是,必须将此外部登录过程作为替代方法实现,而不是唯一的注册方法。
幸运的是, ASP.NET Core Identity 提供了一种机制,允许用户通过外部提供程序(如 Microsoft , Google , Facebook , Twitter 等)执行登录。
使用 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.2 & tabs = 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 直接创建的帐户可以与在 Google , Microsoft , Facebook 等外部机制中创建的用户帐户并存,我们可以在 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": {
"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 应用程序,以便它可以利用用户身份验证,资源保护和敏感页面(如 Basket , Registration 和 Checkout )的优势。
我们了解用户布局流如何工作,并且我们已经学习如何配置 MVC Web 应用程序以满足该流的要求。一旦理解了登录和注销过程,我们就开始修改我们的 MVC 应用程序以使用 id 和用户名,以及每个登录用户的其他注册信息,这些信息在我们的 MVC 应用程序的上下文中表示正在进行购买的客户。
最后,我们学习了如何执行外部登录过程,这使我们能够将 identity 过程与外部服务(如 Microsoft , Google 和 Facebook )中的现有帐户集成,从而为我们的客户提供更方便的注册流程。
原文地址:https://www.codeproject.com/Articles/5129264/ASP-NET-Core-Road-to-Microservices-Part-03-Identit