Part 4 Securing and deploying your applications
第 4 部分:保护和部署应用程序
So far in the book you’ve learned how to use minimal APIs,Razor Pages, and Model-View-Controller (MVC) controllers to build both server-rendered applications and APIs. You know how to dynamically generate JavaScript Object Notation (JSON) and HTML code based on incoming requests, and how to use configuration and dependency injection to customize your app’s behavior at runtime. In part 4 you’ll learn how to add users and profiles to your app and how to publish and secure your apps.
到目前为止,在本书中,您已经学习了如何使用最少的 API、Razor Pages 和模型-视图-控制器 (MVC) 控制器来构建服务器渲染的应用程序和 API。您知道如何根据传入请求动态生成 JavaScript 对象表示法 (JSON) 和 HTML 代码,以及如何使用配置和依赖项注入来自定义应用程序在运行时的行为。在第 4 部分中,您将学习如何将用户和配置文件添加到您的应用程序,以及如何发布和保护您的应用程序。
In chapters 23 through 25 you’ll learn how to protect your applications with authentication and authorization. In chapter 23 you’ll see how you can add ASP.NET Core Identity to your apps so that users can log in and enjoy a customized experience. You’ll learn how to protect your Razor Pages apps using authorization in chapter 24 so that only some users can access certain pages in your app. In chapter 25 you’ll learn how to apply the same protections to your minimal API and web API applications.
在第 23 章到第 25 章中,您将学习如何使用身份验证和授权保护您的应用程序。在第 23 章中,您将了解如何将 ASP.NET Core Identity 添加到您的应用程序中,以便用户可以登录并享受自定义体验。您将在第 24 章中了解如何使用授权保护 Razor Pages 应用程序,以便只有部分用户可以访问应用程序中的某些页面。在第 25 章中,您将学习如何将相同的保护应用于您的最小 API 和 Web API 应用程序。
Adding logging to your application is one of those activities that’s often left until after you discover a problem in production. Adding sensible logging from the get-go will help you quickly diagnose and fix errors as they arise. Chapter 26 introduces the logging framework built into ASP.NET Core. You’ll see how you can use it to write log messages to a wide variety of locations, whether it’s the console, a file, or a third-party remote-logging service.
向应用程序添加日志记录是通常要等到您在生产中发现问题后才进行的活动之一。从一开始就添加合理的日志记录将帮助您快速诊断和修复出现的错误。第 26 章介绍了 ASP.NET Core 中内置的日志记录框架。您将了解如何使用它将日志消息写入各种位置,无论是控制台、文件还是第三方远程日志记录服务。
By this point you’ll have all the fundamentals to build a production application with ASP.NET Core. In chapter 27 I cover the steps required to make your app live, including how to publish an app to Internet Information Services (IIS) and how to configure the URLs your app listens on.
此时,您将拥有使用 ASP.NET Core 构建生产应用程序的所有基础知识。在第 27 章中,我将介绍使您的应用程序上线所需的步骤,包括如何将应用程序发布到 Internet Information Services (IIS) 以及如何配置您的应用程序侦听的 URL。
Before you expose your application to the world, an important part of web development is securing your app correctly. Even if you don’t feel you have any sensitive data in your application, you must make sure to protect your users from attacks by adhering to security best practices. You’ll learn how to configure HTTPS for your application in chapter 28 and why this is a vital step for modern web development. Similarly, in chapter 29 I describe some common security vulnerabilities, how attackers can exploit them, and what you can do to protect your applications.
在向全世界公开您的应用程序之前,Web 开发的一个重要部分是正确保护您的应用程序。即使您认为应用程序中没有任何敏感数据,也必须确保通过遵守安全最佳实践来保护您的用户免受攻击。您将在第 28 章中学习如何为您的应用程序配置 HTTPS,以及为什么这是现代 Web 开发的关键步骤。同样,在第 29 章中,我描述了一些常见的安全漏洞,攻击者如何利用它们,以及您可以采取哪些措施来保护您的应用程序。
23 Authentication: Adding users to your application with Identity
23 身份验证:使用 Identity 将用户添加到您的应用程序
This chapter covers
本章介绍
• Seeing how authentication works in web apps in ASP.NET Core
了解身份验证在 ASP.NET Core中的 Web 应用程序中的工作原理
• Creating a project using the ASP.NET Core Identity system
使用 ASP.NET Core Identity 系统创建项目
• Adding user functionality to an existing web app
向现有 Web 应用程序添加用户功能
• Customizing the default ASP.NET Core Identity UI
自定义默认 ASP.NET Core Identity UI
One of the selling points of a web framework like ASP.NET Core is the ability to provide a dynamic app, customized to individual users. Many apps have the concept of an “account” with the service, which you can “sign in” to and get a different experience.
像 ASP.NET Core 这样的 Web 框架的卖点之一是能够提供针对个人用户定制的动态应用程序。许多应用程序都具有该服务的“帐户”概念,您可以“登录”该帐户并获得不同的体验。
Depending on the service, an account gives you varying things. On some apps you may have to sign in to get access to additional features, and on others you might see suggested articles. On an e-commerce app, you’d be able to place orders and view your past orders; on Stack Overflow you can post questions and answers; on a news site you might get a customized experience based on previous articles you’ve viewed.
根据服务的不同,账户会为您提供不同的内容。在某些应用程序上,您可能必须登录才能访问其他功能,而在其他应用程序上,您可能会看到推荐的文章。在电子商务应用程序上,您将能够下订单并查看您过去的订单;在 Stack Overflow 上,您可以发布问题和答案;在新闻网站上,您可能会根据您以前查看过的文章获得自定义体验。
When you think about adding users to your application, you typically have two aspects to consider:
当您考虑向应用程序添加用户时,通常需要考虑两个方面:
• Authentication—The process of creating users and letting them log in to your app
身份验证 - 创建用户并允许其登录应用程序的过程
• Authorization—Customizing the experience and controlling what users can do, based on the current logged-in user
授权 - 根据当前登录的用户自定义体验并控制用户可以执行的作
In this chapter I’m going to be discussing the first of these points, authentication and membership. In the next chapter I’ll tackle the second point, authorization. In section 23.1 I discuss the difference between authentication and authorization, how authentication works in a traditional ASP.NET Core web app, and ways you can architect your system to provide sign-in functionality. I don’t discuss API applications in detail in this chapter, though many of the authentication principles apply to both styles of app. I discuss API applications chapter 25.
在本章中,我将讨论第一点,身份验证和成员资格。在下一章中,我将讨论第二点,授权。在第 23.1 节中,我将讨论身份验证和授权之间的区别、身份验证在传统 ASP.NET Core Web 应用程序中的工作原理,以及构建系统以提供登录功能的方法。在本章中,我不会详细讨论 API 应用程序,尽管许多身份验证原则适用于这两种类型的应用程序。我将讨论 API 应用程序第 25 章。
In section 23.2 I introduce a user-management system called ASP.NET Core Identity (Identity for short). Identity integrates with Entity Framework Core (EF Core) and provides services for creating and managing users, storing and validating passwords, and signing users in and out of your app.
在 Section 23.2 中,我介绍了一个名为 ASP.NET Core Identity(简称 Identity)的用户管理系统。Identity 与 Entity Framework Core (EF Core) 集成,并提供用于创建和管理用户、存储和验证密码以及让用户登录和注销应用程序的服务。
In section 23.3 you’ll create an app using a default template that includes ASP.NET Core Identity out of the box. This gives you an app to explore and see the features Identity provides, as well as everything it doesn’t.
在 Section 23.3 中,您将使用默认模板创建一个应用程序,该模板包含开箱即用 ASP.NET Core Identity。这为您提供了一个应用程序来探索和查看 Identity 提供的功能,以及它不提供的所有内容。
Creating an app is great for seeing how the pieces fit together, but you’ll often need to add users and authentication to an existing app. In section 23.4 you’ll see the steps required to add ASP.NET Core Identity to an existing app.
创建应用程序非常适合查看各个部分如何组合在一起,但您通常需要向现有应用程序添加用户和身份验证。在 Section 23.4 中,您将看到将 ASP.NET Core Identity 添加到现有应用程序所需的步骤。
In sections 23.5 and 23.6 you’ll learn how to replace pages from the default Identity UI by scaffolding individual pages. In section 23.5 you’ll see how to customize the Razor templates to generate different HTML on the user registration page, and in section 23.6 you’ll learn how to customize the logic associated with a Razor Page. You’ll see how to store additional information about a user (such as their name or date of birth) and how to provide them permissions that you can later use to customize the app’s behavior (if the user is a VIP, for example).
在第 23.5 节和第 23.6 节中,您将学习如何通过搭建单个页面的基架来替换默认 Identity UI 中的页面。在第 23.5 节中,你将了解如何自定义 Razor 模板以在用户注册页上生成不同的 HTML,在第 23.6 节中,你将了解如何自定义与 Razor 页面关联的逻辑。您将了解如何存储有关用户的其他信息(例如他们的姓名或出生日期),以及如何为他们提供稍后可用于自定义应用程序行为的权限(例如,如果用户是 VIP)。
Before we look at the ASP.NET Core Identity system specifically, let’s take a look at authentication and authorization in ASP.NET Core—what’s happening when you sign in to a website and how you can design your apps to provide this functionality.
在我们具体研究 ASP.NET Core Identity 系统之前,让我们先看一下 ASP.NET Core 中的身份验证和授权 - 当您登录网站时会发生什么,以及如何设计您的应用程序来提供此功能。
23.1 Introducing authentication and authorization
23.1 身份验证和授权简介
When you add sign-in functionality to your app and control access to certain functions based on the currently signed-in user, you’re using two distinct aspects of security:
当您向应用添加登录功能并根据当前登录的用户控制对某些功能的访问时,您将使用两个不同的安全性方面:
• Authentication—The process of determining who you are
身份验证 - 确定您是谁的过程
• Authorization—The process of determining what you’re allowed to do
授权 - 确定允许您执行的作的过程
Generally you need to know who the user is before you can determine what they’re allowed to do, so authentication always comes first, followed by authorization. In this chapter we’re looking only at authentication; we’ll cover authorization in chapter 24.
通常,您需要先知道用户是谁,然后才能确定允许他们做什么,因此身份验证始终排在第一位,然后是授权。在本章中,我们只关注身份验证;我们将在第 24 章中介绍授权。
In this section I start by discussing how ASP.NET Core thinks about users, and I cover some of the terminology and concepts that are central to authentication. I found this to be the hardest part to grasp when I learned about authentication, so I’ll take it slow.
在本节中,我首先讨论 ASP.NET Core 如何看待用户,并介绍一些对身份验证至关重要的术语和概念。当我了解身份验证时,我发现这是最难掌握的部分,因此我会慢慢来。
Next, we’ll look at what it means to sign in to a traditional web app. After all, you only provide your password and sign into an app on a single page; how does the app know the request came from you for subsequent requests?
接下来,我们将了解登录到传统 Web 应用程序意味着什么。毕竟,您只需在单个页面上提供密码并登录应用程序;应用程序如何知道您的后续请求来自您?
23.1.1 Understanding users and claims in ASP.NET Core
23.1.1 了解 ASP.NET Core 中的用户和声明
The concept of a user is baked into ASP.NET Core. In chapter 3 you learned that the HTTP server, Kestrel, creates an HttpContext object for every request it receives. This object is responsible for storing all the details related to that request, such as the request URL, any headers sent, and the body of the request.
用户的概念已融入 ASP.NET Core。在第 3 章中,您了解了 HTTP 服务器 Kestrel 为它收到的每个请求创建一个 HttpContext 对象。此对象负责存储与该请求相关的所有详细信息,例如请求 URL、发送的任何标头以及请求正文。
The HttpContext object also exposes the current principal for a request as the User property. This is ASP.NET Core’s view of which user made the request. Any time your app needs to know who the current user is or what they’re allowed to do, it can look at the HttpContext.User principal.
HttpContext 对象还将请求的当前主体公开为 User 属性。这是 ASP.NET Core 对哪个用户发出请求的视图。每当你的应用程序需要知道当前用户是谁或允许他们做什么时,它都可以查看 HttpContext.User 主体。
DEFINITION You can think of the principal as the user of your app.
定义:您可以将主体视为应用程序的用户。
In ASP.NET Core, principals are implemented using the ClaimsPrincipal class, which has a collection of claims associated with it, as shown in figure 23.1.
在 ASP.NET Core 中,主体是使用 ClaimsPrincipal 类实现的,该类具有与之关联的声明集合,如图 23.1 所示。
Figure 23.1 The principal is the current user, implemented as ClaimsPrincipal. It contains a collection of Claims that describe the user.
图 23.1 主体是当前用户,实现为 ClaimsPrincipal。它包含描述用户的 Claims 集合。
You can think about claims as properties of the current user. For example, you could have claims for things like email, name, and date of birth.
您可以将声明视为当前用户的属性。例如,您可以对电子邮件、姓名和出生日期等内容提出索赔。
DEFINITION A claim is a single piece of information about a principal; it consists of a claim type and an optional value.
定义:索赔是有关委托人的单个信息;它由 Claim 类型和 Optional Value 组成。
Claims can also be indirectly related to permissions and authorization, so you could have a claim called HasAdminAccess or IsVipCustomer. These would be stored in the same way—as claims associated with the user principal.
声明也可以与权限和授权间接相关,因此您可以有一个名为 HasAdminAccess 或 IsVipCustomer 的声明。这些请求的存储方式与与用户主体关联的声明相同。
NOTE Earlier versions of ASP.NET used a role-based approach to security rather than a claims-based approach. The ClaimsPrincipal used in ASP.NET Core is compatible with this approach for legacy reasons, but you should use the claims-based approach for new apps.
注意:早期版本的 ASP.NET 使用基于角色的安全方法,而不是基于声明的方法。由于遗留原因,ASP.NET Core 中使用的 ClaimsPrincipal 与此方法兼容,但对于新应用,应使用基于声明的方法。
Kestrel assigns a user principal to every request that arrives at your app. Initially, that principal is a generic, anonymous, unauthenticated principal with no claims. How do you log in, and how does ASP.NET Core know that you’ve logged in on subsequent requests?
Kestrel 为到达应用程序的每个请求分配一个用户主体。最初,该委托人是通用的、匿名的、未经身份验证的委托人,没有声明。您如何登录,ASP.NET Core 如何知道您已登录后续请求?
In the next section we’ll look at how authentication works in a traditional web app using ASP.NET Core and the process of signing into a user account.
在下一节中,我们将了解使用 ASP.NET Core 在传统 Web 应用程序中进行身份验证的工作原理,以及登录用户帐户的过程。
23.1.2 Authentication in ASP.NET Core: Services and middleware
23.1.2 ASP.NET Core 中的身份验证:服务和中间件
Adding authentication to any web app involves a few moving parts. The same general process applies whether you’re building a traditional web app or a client-side app (though there are often differences in the latter, as I discuss in chapter 25):
向任何 Web 应用程序添加身份验证都涉及一些移动部件。无论您是构建传统的 Web 应用程序还是客户端应用程序,相同的一般过程都适用(尽管后者经常存在差异,正如我在第 25 章中讨论的那样):
-
The client sends an identifier and a secret to the app to identify the current user. For example, you could send an email address (identifier) and a password (secret).
客户端向应用程序发送标识符和密钥以识别当前用户。例如,您可以发送电子邮件地址 (identifier) 和密码 (secret)。 -
The app verifies that the identifier corresponds to a user known by the app and that the corresponding secret is correct.
应用程序验证标识符是否对应于应用程序已知的用户,以及相应的密钥是否正确。 -
If the identifier and secret are valid, the app can set the principal for the current request, but it also needs a way of storing these details for subsequent requests. For traditional web apps, this is typically achieved by storing an encrypted version of the user principal in a cookie.
如果标识符和密钥有效,则应用程序可以为当前请求设置主体,但它还需要一种方法来存储这些详细信息以供后续请求使用。对于传统的 Web 应用程序,这通常是通过将用户主体的加密版本存储在 Cookie 中来实现的。
This is the typical flow for most web apps, but in this section I’m going to look at how it works in ASP.NET Core. The overall process is the same, but it’s good to see how this pattern fits into the services, middleware, and Model-View-Controller (MVC) aspects of an ASP.NET Core application. We’ll step through the various pieces at play in a typical app when you sign in as a user, what that means, and how you can make subsequent requests as that user.
这是大多数 Web 应用程序的典型流程,但在本节中,我将介绍它在 ASP.NET Core 中的工作原理。整个过程是相同的,但很高兴看到此模式如何适应 ASP.NET Core 应用程序的服务、中间件和模型-视图-控制器 (MVC) 方面。我们将逐步介绍当您以用户身份登录时,典型应用程序中的各个部分、这意味着什么,以及您如何以该用户身份发出后续请求。
Signing in to an ASP.NET Core application
登录到 ASP.NET Core 应用程序
When you first arrive on a site and sign in to a traditional web app, the app will send you to a sign-in page and ask you to enter your username and password. After you submit the form to the server, the app redirects you to a new page, and you’re magically logged in! Figure 23.2 shows what’s happening behind the scenes in an ASP.NET Core app when you submit the form.
当您首次访问站点并登录到传统的 Web 应用程序时,该应用程序会将您转到登录页面,并要求您输入用户名和密码。将表单提交到服务器后,应用程序会将您重定向到新页面,然后您神奇地登录了!图 23.2 显示了当您提交表单时 ASP.NET Core 应用程序中的幕后情况。
Figure 23.2 Signing in to an ASP.NET Core application. SignInManager is responsible for setting HttpContext.User to the new principal and serializing the principal to the encrypted cookie.
图 23.2 登录到 ASP.NET Core 应用程序。SignInManager 负责将 HttpContext.User 设置为新主体,并将主体序列化为加密的 Cookie。
This figure shows the series of steps from the moment you submit the login form on a Razor Page to the point the redirect is returned to the browser. When the request first arrives, Kestrel creates an anonymous user principal and assigns it to the HttpContext.User property. The request is then routed to the Login.cshtml Razor Page, which reads the email and password from the request using model binding.
此图显示了从您在 Razor 页面上提交登录表单到将重定向返回到浏览器的一系列步骤。当请求首次到达时,Kestrel 会创建一个匿名用户主体,并将其分配给 HttpContext.User 属性。然后,该请求将路由到 Login.cshtml Razor 页面,该页面使用模型绑定从请求中读取电子邮件和密码。
The meaty work happens inside the SignInManager service. This is responsible for loading a user entity with the provided username from the database and validating that the password they provided is correct.
繁重的工作发生在 SignInManager 服务内部。这负责使用从数据库中提供的用户名加载用户实体,并验证他们提供的密码是否正确。
Warning Never store passwords in the database directly. They should be hashed using a strong one-way algorithm. The ASP.NET Core Identity system does this for you, but it’s always wise to reiterate this point!
警告:切勿将密码直接存储在数据库中。它们应该使用强大的单向算法进行哈希处理。ASP.NET Core Identity 系统为您执行此作,但重申这一点始终是明智的!
If the password is correct, SignInManager creates a new ClaimsPrincipal from the user entity it loaded from the database and adds the appropriate claims, such as the email address. It then replaces the old, anonymous HttpContext.User principal with the new, authenticated principal.
如果密码正确,SignInManager 将从它从数据库中加载的用户实体创建新的 ClaimsPrincipal,并添加相应的声明,例如电子邮件地址。然后,它将旧的匿名 HttpContext.User 主体替换为经过身份验证的新主体。
Finally, SignInManager serializes the principal, encrypts it, and stores it as a cookie. A cookie is a small piece of text that’s sent back and forth between the browser and your app along with each request, consisting of a name and a value.
最后,SignInManager 序列化主体,对其进行加密,并将其存储为 Cookie。Cookie 是一小段文本,它与每个请求一起在浏览器和应用程序之间来回发送,由名称和值组成。
This authentication process explains how you can set the user for a request when they first log in to your app, but what about subsequent requests? You send your password only when you first log in to an app, so how does the app know that it’s the same user making the request?
此身份验证过程说明了如何在用户首次登录您的应用程序时为用户设置请求,但后续请求呢?您仅在首次登录应用程序时发送密码,那么该应用程序如何知道它是发出请求的同一用户?
Authenticating users for subsequent requests
为后续请求对用户进行身份验证The key to persisting your identity across multiple requests lies in the final step of figure 23.2, where you serialized the principal in a cookie. Browsers automatically send this cookie with all requests made to your app, so you don’t need to provide your password with every request.
在多个请求中保留身份的关键在于图 23.2 的最后一步,在该步骤中,您在 cookie 中序列化了主体。浏览器会自动将此 Cookie 与向您的应用发出的所有请求一起发送,因此您无需为每个请求提供密码。
ASP.NET Core uses the authentication cookie sent with the requests to rehydrate a ClaimsPrincipal and set the HttpContext.User principal for the request, as shown in figure 23.3. The important thing to note is when this process happens—in the AuthenticationMiddleware.
ASP.NET Core 使用随请求发送的身份验证 Cookie 来解除冻结 ClaimsPrincipal 并为请求设置 HttpContext.User 主体,如图 23.3 所示。需要注意的重要一点是此过程何时发生 — 在 AuthenticationMiddleware 中。
Figure 23.3 A subsequent request after signing in to an application. The cookie sent with the request contains the user principal, which is validated and used to authenticate the request.
图 23.3 登录应用程序后的后续请求。随请求发送的 Cookie 包含用户主体,该主体经过验证并用于对请求进行身份验证。
When a request containing the authentication cookie is received, Kestrel creates the default, unauthenticated, anonymous principal and assigns it to the HttpContext.User principal. Any middleware that runs before the AuthenticationMiddleware sees the request as unauthenticated, even if there’s a valid cookie.
收到包含身份验证 Cookie 的请求时,Kestrel 会创建默认的、未经身份验证的匿名主体,并将其分配给 HttpContext.User 主体。在 AuthenticationMiddleware 之前运行的任何中间件都会将请求视为未经身份验证,即使存在有效的 cookie。
Tip If it looks like your authentication system isn’t working, double-check your middleware pipeline. Only middleware that runs after AuthenticationMiddleware will see the request as authenticated.
提示:如果您的身份验证系统看起来无法正常工作,请仔细检查您的中间件管道。只有在 AuthenticationMiddleware 之后运行的中间件才会看到请求经过身份验证。
The AuthenticationMiddleware is responsible for setting the current user for a request. The middleware calls the authentication services, which reads the cookie from the request, decrypts it, and deserializes it to obtain the ClaimsPrincipal created when the user logged in.
AuthenticationMiddleware 负责为请求设置当前用户。中间件调用身份验证服务,该服务从请求中读取 Cookie,对其进行解密,然后对其进行反序列化,以获取在用户登录时创建的 ClaimsPrincipal。
The AuthenticationMiddleware sets the HttpContext.User principal to the new, authenticated principal. All subsequent middleware now knows the user principal for the request and can adjust its behavior accordingly (for example, displaying the user’s name on the home page or restricting access to some areas of the app).
AuthenticationMiddleware 将 HttpContext.User 主体设置为经过身份验证的新主体。现在,所有后续中间件都知道请求的用户主体,并可以相应地调整其行为(例如,在主页上显示用户名或限制对应用程序某些区域的访问)。
NOTE The AuthenticationMiddleware is responsible only for authenticating incoming requests and setting the ClaimsPrincipal if the request contains an authentication cookie. It is not responsible for redirecting unauthenticated requests to the login page or rejecting unauthorized requests; that is handled by the AuthorizationMiddleware, as you’ll see in chapter 24.
注意:AuthenticationMiddleware 只负责对传入请求进行身份验证,并在请求包含身份验证 Cookie 时设置 ClaimsPrincipal。它不负责将未经身份验证的请求重定向到登录页面或拒绝未经授权的请求;它由 AuthorizationMiddleware 处理,您将在第 24 章中看到。
The process described so far, in which a single app authenticates the user when they log in and sets a cookie that’s read on subsequent requests, is common with traditional web apps, but it isn’t the only possibility. In chapter 25 we’ll take a look at authentication for web API applications, used by client-side and mobile apps and at how the authentication system changes for those scenarios.
到目前为止描述的过程,即单个应用程序在用户登录时对用户进行身份验证,并设置在后续请求中读取的 cookie,这在传统 Web 应用程序中很常见,但并不是唯一的可能性。在第 25 章中,我们将介绍客户端和移动应用程序使用的 Web API 应用程序的身份验证,以及这些场景的身份验证系统如何变化。
Another thing to consider is where you store the authentication details for users of your app. In figure 23.2 I showed the authentication services loading the user authentication details from your app’s database, but that’s only one option.
要考虑的另一件事是存储应用程序用户的身份验证详细信息的位置。在图 23.2 中,我展示了从应用程序数据库中加载用户身份验证详细信息的身份验证服务,但这只是一个选项。
Another option is to delegate the authentication responsibilities to a third-party identity provider, such as Okta, Auth0, Azure Active Directory B2B/B2C, or even Facebook. These manage users for you, so user information and passwords are stored in their database rather than your own. The biggest advantage of this approach is that you don’t have to worry about making sure your customer data is safe; you can be pretty sure that a third party will protect it, as it’s their whole business.
另一种选择是将身份验证责任委托给第三方身份提供商,例如 Okta、Auth0、Azure Active Directory B2B/B2C 甚至 Facebook。这些 Bug 会为您管理用户,因此用户信息和密码存储在他们的数据库中,而不是您自己的数据库中。这种方法的最大优点是您不必担心确保客户数据的安全;您可以非常确定第三方会保护它,因为这是他们的全部业务。
Tip Wherever possible, I recommend this approach, as it delegates security responsibilities to someone else. You can’t lose your users’ details if you never had them! Make sure to understand the differences in providers, however. With a provider like Auth0, you would own the profiles created, whereas with a provider like Facebook, you don’t!
提示:我尽可能推荐这种方法,因为它将安全责任委托给其他人。如果您从未拥有用户的详细信息,您就不会丢失它们!但是,请务必了解提供程序之间的差异。使用像 Auth0 这样的提供商,您将拥有创建的配置文件,而使用像 Facebook 这样的提供商,您将不拥有!
Each provider provides instructions on how to integrate with their identity services, ideally using the OpenID Connect (OIDC) specification. This typically involves configuring some authentication services in your application, adding some configuration, and delegating the authentication process itself to the external provider. These providers can be used with your API apps too, as I discuss in chapter 25.
每个提供商都提供了有关如何与其身份服务集成的说明,最好使用 OpenID Connect (OIDC) 规范。这通常涉及在应用程序中配置一些身份验证服务、添加一些配置以及将身份验证过程本身委托给外部提供商。这些提供程序也可以用于您的 API 应用程序,正如我在第 25 章中讨论的那样。
NOTE Hooking up your apps and APIs to use an identity provider can require a fair amount of tedious configuration, both in the app and the identity provider, but if you follow the provider’s documentation you should have plain sailing. For example, you can follow the documentation for adding authentication to a traditional web app using Microsoft’s Identity Platform here: http://mng.bz/4D9w.
注意:将应用程序和 API 挂接以使用身份提供商可能需要在应用程序和身份提供商中进行大量繁琐的配置,但如果您遵循提供商的文档,您应该会一帆风顺。例如,您可以按照以下文档使用 Microsoft 的 Identity Platform 将身份验证添加到传统 Web 应用程序:http://mng.bz/4D9w。
While I recommend using an external identity provider where possible, sometimes you really want to store all the authentication details of your users directly in your app. That’s the approach I describe in this chapter.
虽然我建议尽可能使用外部身份提供商,但有时您确实希望将用户的所有身份验证详细信息直接存储在您的应用程序中。这就是我在本章中描述的方法。
ASP.NET Core Identity (hereafter shortened to Identity) is a system that makes building the user-management aspect of your app. It handles all the boilerplate for saving and loading users to a database, as well as best practices for security, such as user lockout, password hashing, and multifactor authentication.
ASP.NET Core Identity(以下简称 Identity)是一个用于构建应用程序的用户管理方面的系统。它处理将用户保存和加载到数据库的所有样板,以及安全性最佳实践,例如用户锁定、密码哈希和多重身份验证。
DEFINITION Multifactor authentication (MFA), and the subset two-factor authentication (2FA) require both a password and an extra piece of information to sign in. This could involve sending a code to a user’s phone by Short Message Service (SMS) or using a mobile app to generate a code, for example.
定义:多重身份验证 (MFA) 和子集双重身份验证 (2FA) 需要密码和额外的信息才能登录。例如,这可能涉及通过短信服务 (SMS) 向用户的手机发送验证码,或使用移动应用生成验证码。
In the next section I’m going to talk about the ASP.NET Core Identity system, the problems it solves, when you’d want to use it, and when you might not want to use it. In section 23.3 we take a look at some code and see ASP.NET Core Identity in action.
在下一节中,我将讨论 ASP.NET Core Identity 系统、它解决的问题、何时要使用它以及何时可能不想使用它。在 Section 23.3 中,我们看了一些代码,并看到了 Core Identity ASP.NET 实际应用。
23.2 What is ASP.NET Core Identity?
23.2 什么是 ASP.NET Core Identity?
Whenever you need to add nontrivial behaviors to your application, you typically need to add users and authentication. That means you’ll need a way of persisting details about your users, such as their usernames and passwords.
每当需要向应用程序添加重要行为时,通常需要添加用户和身份验证。这意味着您需要一种方法来保留有关用户的详细信息,例如他们的用户名和密码。
This might seem like a relatively simple requirement, but given that this is related to security and people’s personal details, it’s important you get it right. As well as storing the claims for each user, it’s important to store passwords using a strong hashing algorithm to allow users to use MFA where possible and to protect against brute-force attacks, to name a few of the many requirements. Although it’s perfectly possible to write all the code to do this manually and to build your own authentication and membership system, I highly recommend you don’t.
这似乎是一个相对简单的要求,但考虑到这与安全和人们的个人详细信息有关,因此请务必正确处理。除了存储每个用户的声明外,使用强大的哈希算法存储密码也很重要,这样用户就可以尽可能使用 MFA 并防止暴力攻击,仅举几例。尽管完全可以编写所有代码来手动执行此作并构建您自己的身份验证和成员资格系统,但我强烈建议您不要这样做。
I’ve already mentioned third-party identity providers such as Auth0 and Azure Active Directory. These Software as a Service (SaaS) solutions take care of the user-management and authentication aspects of your app for you. If you’re in the process of moving apps to the cloud generally, solutions like these can make a lot of sense.
我已经提到了第三方身份提供商,例如 Auth0 和 Azure Active Directory。这些软件即服务 (SaaS) 解决方案为您处理应用程序的用户管理和身份验证方面。如果您通常正在将应用程序迁移到云,那么像这样的解决方案可能非常有意义。
If you can’t or don’t want to use these third-party solutions, I recommend you consider using the ASP.NET Core Identity system to store and manage user details in your database. ASP.NET Core Identity takes care of most of the boilerplate associated with authentication, but it remains flexible and lets you control the login process for users if you need to.
如果您不能或不想使用这些第三方解决方案,我建议您考虑使用 ASP.NET Core Identity 系统在您的数据库中存储和管理用户详细信息。ASP.NET Core Identity 负责与身份验证相关的大部分样板,但它仍然保持灵活性,并允许您根据需要控制用户的登录过程。
NOTE ASP.NET Core Identity is an evolution of the legacy .NET Framework ASP.NET Identity system, with some design improvements and update to work with ASP.NET Core.
注意: ASP.NET Core Identity 是旧版 .NET Framework ASP.NET Identity 系统的演变,经过一些设计改进和更新以与 ASP.NET Core 配合使用。
By default, ASP.NET Core Identity uses EF Core to store user details in the database. If you’re already using EF Core in your project, this is a perfect fit. Alternatively, it’s possible to write your own stores for loading and saving user details in another way.
默认情况下,ASP.NET Core Identity 使用 EF Core 将用户详细信息存储在数据库中。如果你已在项目中使用 EF Core,则这是一个完美的选择。或者,可以编写自己的 store 以另一种方式加载和保存用户详细信息。
Identity takes care of the low-level parts of user management, as shown in table 23.1. As you can see from this list, Identity gives you a lot, but not everything—by a long shot!
Identity 负责用户 Management 的低级部分,如 Table 23.1 所示。从这个列表中可以看出,Identity 能给你很多,但不是全部——很长一段时间!
Table 23.1 Which services are and aren’t handled by ASP.NET Core Identity
表 23.1 哪些服务由 ASP.NET Core Identity 处理,哪些服务不由 Core Identity 处理
Managed by ASP.NET Core Identity | Requires implementing by the developer |
---|---|
Database schema for storing users and claims | UI for logging in, creating, and managing users (Razor Pages or controllers); included in an optional package that provides a default UI |
Creating a user in the database | Sending email messages |
Password validation and rules | Customizing claims for users (adding new claims) |
Handling user account lockout (to prevent brute-force attacks) | Configuring third-party identity providers |
Managing and generating MFA/2FA codes | Integration into MFA such as sending SMS messages, time-based one-time password (TOTP) authenticator apps, or hardware keys |
Generating password-reset tokens | - |
Saving additional claims to the database | - |
Managing third-party identity providers (for example, Facebook, Google, and Twitter) | - |
The biggest missing piece is the fact that you need to provide all the UI for the application, as well as tying all the individual Identity services together to create a functioning sign-in process. That’s a big missing piece, but it makes the Identity system extremely flexible.
最大的缺失部分是您需要为应用程序提供所有 UI,以及将所有单独的 Identity 服务捆绑在一起以创建有效的登录过程。这是一个很大的缺失部分,但它使 Identity 系统非常灵活。
Luckily, ASP.NET Core includes a helper NuGet library, Microsoft.AspNetCore.Identity.UI, that gives you the whole of the UI boilerplate for free. That’s over 30 Razor Pages with functionality for logging in, registering users, using 2FA, and using external login providers, among other features. You can still customize these pages if you need to, but having a whole login process working out of the box, with no code required on your part, is a huge win. We’ll look at this library and how you use it in sections 23.3 and 23.4.
幸运的是,ASP.NET Core 包含一个帮助程序 NuGet 库 Microsoft.AspNetCore.Identity.UI,它免费为您提供整个 UI 样板。这是 30 多个 Razor 页面,具有登录、注册用户、使用 2FA 和使用外部登录提供程序等功能。如果需要,您仍然可以自定义这些页面,但是拥有一个开箱即用的整个登录过程,而无需您编写任何代码,这是一个巨大的胜利。我们将在 23.3 和 23.4 节中介绍这个库以及你如何使用它。
For that reason, I strongly recommend using the default UI as a starting point, whether you’re creating an app or adding user management to an existing app. But the question remains as to when you should use Identity and when you should consider rolling your own.
因此,我强烈建议使用默认 UI 作为起点,无论您是创建应用程序还是向现有应用程序添加用户管理。但问题仍然存在,何时应该使用 Identity 以及何时应该考虑推出自己的 Identity。
I’m a big fan of Identity when you need to store your own users, so I tend to suggest it in most situations, as it handles a lot of security-related things for you that are easy to mess up. I’ve heard several arguments against it, some valid and others less so:
当您需要存储自己的用户时,我是 Identity 的忠实粉丝,因此我倾向于在大多数情况下建议使用它,因为它可以为您处理很多与安全相关的事情,这些事情很容易搞砸。我听到了几个反对它的论点,有些是有效的,有些则不太有效:
• I already have user authentication in my app. Great! In that case, you’re probably right, Identity may not be necessary. But does your custom implementation use MFA? Do you have account lockout? If not, and if you need to add them, considering Identity may be worthwhile.
我的应用程序中已经有用户身份验证。太好了!在这种情况下,您可能是对的,Identity 可能不是必需的。但是您的自定义实施是否使用 MFA?您是否有帐户锁定?如果没有,并且您需要添加它们,考虑 Identity 可能是值得的。
• I don’t want to use EF Core. That’s a reasonable stance. You could be using Dapper, some other object-relational mapper (ORM), or even a document database for your database access. Luckily, the database integration in Identity is pluggable, so you could swap out the EF Core integration and use your own database integration libraries instead.
我不想使用 EF Core。这是一个合理的立场。您可以使用 Dapper、其他一些对象关系映射器 (ORM),甚至是文档数据库来访问数据库。幸运的是,Identity 中的数据库集成是可插拔的,因此您可以换掉 EF Core 集成并改用自己的数据库集成库。
• My use case is too complex for Identity. Identity provides lower-level services for authentication, so you can compose the pieces however you like. It’s also extensible, so if you need to, for example, transform claims before creating a principal, you can.
我的用例对于 Identity 来说太复杂了。Identity 提供较低级别的身份验证服务,因此您可以根据自己的喜好组合各个部分。它也是可扩展的,因此,如果需要在创建主体之前转换声明,则可以。
• I don’t like the default Razor Pages UI. The default UI for Identity is entirely optional. You can still use the Identity services and user management but provide your own UI for logging in and registering users. However, be aware that although doing this gives you a lot of flexibility, it’s also easy to introduce a security flaw in your user-management system—the last place you want security flaws!
我不喜欢默认的 Razor Pages UI。Identity 的默认 UI 完全是可选的。您仍然可以使用 Identity 服务和用户管理,但提供自己的 UI 来登录和注册用户。但是,请注意,尽管这样做可以为您提供很大的灵活性,但也很容易在用户管理系统中引入安全漏洞 - 这是您最不希望出现安全漏洞的地方!
• I’m not using Bootstrap to style my application. The default Identity UI uses Bootstrap as a styling framework, the same as the default ASP.NET Core templates. Unfortunately, you can’t easily change that, so if you’re using a different framework or need to customize the HTML generated, you can still use Identity, but you’ll need to provide your own UI.
我没有使用 Bootstrap 来设置应用程序的样式。默认身份 UI 使用 Bootstrap 作为样式框架,与默认的 ASP.NET Core 模板相同。遗憾的是,您无法轻松更改此设置,因此,如果您使用的是其他框架或需要自定义生成的 HTML,您仍然可以使用 Identity,但需要提供自己的 UI。
• I don’t want to build my own identity system. I’m glad to hear it. Using an external identity provider like Azure Active Directory or Auth0 is a great way of shifting the responsibility and risk associated with storing users’ personal information to a third party.
我不想构建自己的身份系统。我很高兴听到这个消息。使用 Azure Active Directory 或 Auth0 等外部身份提供商是将与存储用户个人信息相关的责任和风险转移给第三方的好方法。
Any time you’re considering adding user management to your ASP.NET Core application, I’d recommend looking at Identity as a great option for doing so. In the next section I’ll demonstrate what Identity provides by creating a new Razor Pages application using the default Identity UI. In section 23.4 we’ll take that template and apply it to an existing app instead, and in sections 23.5 and 23.6 you’ll see how to override the default pages.
每当你考虑向 ASP.NET Core 应用程序添加用户管理时,我建议将 Identity 视为一个不错的选择。在下一节中,我将通过使用默认标识 UI 创建新的 Razor Pages 应用程序来演示标识提供的功能。在 23.4 节中,我们将获取该模板并将其应用于现有应用程序,在 23.5 和 23.6 节中,您将看到如何覆盖默认页面。
23.3 Creating a project that uses ASP.NET Core Identity
23.3 创建使用 ASP.NET Core Identity 的项目
I’ve covered authentication and Identity in general terms, but the best way to get a feel for it is to see some working code. In this section we’re going to look at the default code generated by the ASP.NET Core templates with Identity, how the project works, and where Identity fits in.
我已经大致介绍了身份验证和标识,但了解它的最佳方法是查看一些工作代码。在本节中,我们将了解使用 Identity 的 ASP.NET Core 模板生成的默认代码、项目的工作原理以及 Identity 的适用范围。
23.3.1 Creating the project from a template
23.3.1 从模板创建项目
You’ll start by using the Visual Studio templates to generate a simple Razor Pages application that uses Identity for storing individual user accounts in a database.
首先,使用 Visual Studio 模板生成一个简单的 Razor Pages 应用程序,该应用程序使用 Identity 将各个用户帐户存储在数据库中。
Tip You can create a similar project using the .NET CLI by running dotnet new webapp -au Individual. The Visual Studio template uses a LocalDB database, but the dotnet new template uses SQLite by default. To use LocalDB instead, run dotnet new webapp -au Individual --use-local-db.
提示:您可以通过运行 dotnet new webapp -au Individual 来使用 .NET CLI 创建类似的项目。Visual Studio 模板使用 LocalDB 数据库,但 dotnet 新模板默认使用 SQLite。要改用 LocalDB,请运行 dotnet new webapp -au Individual --use-local-db。
To create the template using Visual Studio, you must be using the 2022 version or later and have the .NET 7 software development kit (SDK) installed. Follow these steps:
要使用 Visual Studio 创建模板,您必须使用 2022 版本或更高版本,并安装 .NET 7 软件开发工具包 (SDK)。请执行以下步骤:
-
Choose
File > New > Project
or choose Create a New Project on the splash screen.
在初始屏幕上选择File > New > Project
创建新项目。 -
From the list of templates, choose ASP.NET Core Web Application, ensuring that you select the C# language template.
从模板列表中,选择 ASP.NET Core Web Application(核心 Web 应用程序),确保选择 C# 语言模板。 -
On the next screen, enter a project name, location, and a solution name, and choose Create.
在下一个屏幕上,输入项目名称、位置和解决方案名称,然后选择 Create (创建)。 -
On the Additional Information screen, change the Authentication type to Individual Accounts, as shown in figure 23.4. Leave the other settings at their defaults, and choose Create to create the application.
在 Additional Information 屏幕上,将 Authentication type 更改为 Individual Accounts,如图 23.4 所示。将其他设置保留为默认值,然后选择 Create (创建) 以创建应用程序。
Visual Studio automatically runs dotnet restore to restore all the necessary NuGet packages for the project.
Visual Studio 会自动运行 dotnet restore 来还原项目所需的所有 NuGet 包。
Figure 23.4 Choosing the authentication mode of the new ASP.NET Core application template in VS 2022
图 23.4 在 VS 2022 中选择新 ASP.NET Core 应用程序模板的身份验证模式
- Run the application to see the default app, as shown in figure 23.5.
运行应用程序以查看默认应用程序,如图 23.5 所示。
NOTE The Visual Studio template configures the application to use LocalDB and includes EF Core migrations for SQL Server. If you want to use a different database provider, you can replace the configuration and migrations with your database of choice, as described in chapter 12.
注意:Visual Studio 模板将应用程序配置为使用 LocalDB,并包括 SQL Server 的 EF Core 迁移。如果要使用不同的数据库提供程序,可以将配置和迁移替换为您选择的数据库,如第 12 章所述。
Figure 23.5 The default template with individual account authentication looks similar to the no authentication template, with the addition of a Login widget at the top right of the page.
图 23.5 具有个人帐户身份验证的默认模板看起来类似于无身份验证模板,只是在页面右上角添加了一个 Login 小部件。
This template should look familiar, with one twist: you now have Register and Login buttons! Feel free to play with the template—creating a user, logging in and out—to get a feel for the app. Once you’re happy, look at the code generated by the template and the boilerplate it saved you from writing.
这个模板应该看起来很熟悉,但有一个变化:您现在有 Register 和 Login 按钮了!您可以随意使用模板 — 创建用户、登录和注销 — 以感受应用程序。满意后,请查看模板生成的代码以及它使您免于编写的样板。
Tip Don’t forget to run the included EF Core migrations before trying to create users. Run dotnet ef database update from the project folder.
提示:在尝试创建用户之前,请不要忘记运行包含的 EF Core 迁移。从项目文件夹运行 dotnet ef database update。
23.3.2 Exploring the template in Solution Explorer
23.3.2 在解决方案资源管理器中浏览模板
The project generated by the template, shown in figure 23.6, is similar to the default no-authentication template. That’s largely due to the default UI library, which brings in a big chunk of functionality without exposing you to the nitty-gritty details.
该模板生成的项目(如图 23.6 所示)类似于默认的 no-authentication 模板。这主要是由于默认的 UI 库,它带来了大量功能,而不会让您了解细节。
Figure 23.6 The project layout of the default template with individual authentication
图 23.6 使用单独身份验证的默认模板的项目布局
The biggest addition is the Areas folder in the root of your project, which contains an Identity subfolder. Areas are sometimes used for organizing sections of functionality. Each area can contain its own Pages folder, which is analogous to the main Pages folder in your application.
最大的新增功能是项目根目录中的 Areas 文件夹,其中包含一个 Identity 子文件夹。区域有时用于组织功能部分。每个区域都可以包含自己的 Pages 文件夹,该文件夹类似于应用程序中的主 Pages 文件夹。
DEFINITION Areas are used to group Razor Pages into separate hierarchies for organizational purposes. I rarely use areas and prefer to create subfolders in the main Pages folder instead. The one exception is the Identity UI, which uses a separate Identity area by default. For more details on areas, see Microsoft’s “Areas in ASP.NET Core” documentation: http://mng.bz/7Vw9.
定义:区域用于将 Razor 页面分组到单独的层次结构中,以便进行组织。我很少使用区域,更喜欢在主 Pages 文件夹中创建子文件夹。一个例外是 Identity UI,默认情况下,它使用单独的 Identity 区域。有关区域的更多详细信息,请参阅 Microsoft 的“ASP.NET Core 中的区域”文档:http://mng.bz/7Vw9。
The Microsoft.AspNetCore.Identity.UI package creates Razor Pages in the Identity area. You can override any page in this default UI by creating a corresponding page in the Areas/Identity/Pages folder in your application. In figure 23.6, the default template adds a _ViewStart.cshtml file that overrides the template that is included as part of the default UI. This file contains the following code, which sets the default Identity UI Razor Pages to use your project’s default _Layout.cshtml file:
Microsoft.AspNetCore.Identity.UI 包在“标识”区域中创建 Razor Pages。您可以通过在应用程序的 Areas/Identity/Pages 文件夹中创建相应的页面来覆盖此默认 UI 中的任何页面。在图 23.6 中,默认模板添加了一个 _ViewStart.cshtml 文件,该文件将替代作为默认 UI 的一部分包含的模板。此文件包含以下代码,该代码将默认标识 UI Razor 页面设置为使用项目的默认 _Layout.cshtml 文件:
@{
Layout = "/Pages/Shared/_Layout.cshtml";
}
Some obvious questions at this point are “How do you know what’s included in the default UI?” and “Which files can you override?” You’ll see the answers to both in section 23.5, but in general you should try to avoid overriding files where possible. After all, the goal with the default UI is to reduce the amount of code you have to write!
此时,一些明显的问题是“您如何知道默认 UI 中包含哪些内容”和“您可以覆盖哪些文件?您将在 Section 23.5 中看到这两个问题的答案,但一般来说,您应该尽可能避免覆盖文件。毕竟,默认 UI 的目标是减少您必须编写的代码量!
The Data folder in your new project template contains your application’s EF Core DbContext, called ApplicationDbContext, and the migrations for configuring the database schema to use Identity. I’ll discuss this schema in more detail in section 23.3.3.
新项目模板中的 Data 文件夹包含应用程序的 EF Core DbContext(称为 ApplicationDbContext)和用于将数据库架构配置为使用 Identity 的迁移。我将在 Section 23.3.3 中更详细地讨论这个模式。
The final additional file included in this template compared with the no-authentication version is the partial Razor view Pages/Shared/_LoginPartial.cshtml. This provides the Register and Login links you saw in figure 23.5, and it’s rendered in the default Razor layout, _Layout.cshtml.
与无身份验证版本相比,此模板中包含的最后一个附加文件是部分 Razor 视图 Pages/Shared/_LoginPartial.cshtml。这提供了你在图 23.5 中看到的 Register 和 Login 链接,并呈现在默认的 Razor 布局 _Layout.cshtml 中。
If you look inside _LoginPartial.cshtml, you can see how routing works with areas by combining the Razor Page path with an {area} route parameter using Tag Helpers. For example, the Login link specifies that the Razor Page /Account/Login is in the Identity area using the asp-area attribute:
如果查看 _LoginPartial.cshtml,则可以通过使用标记帮助程序将 Razor Page 路径与 {area} 路由参数组合在一起,了解路由如何与区域配合使用。例如,Login 链接使用 asp-area 属性指定 Razor Page /Account/Login 位于 Identity 区域中:
<a asp-area="Identity" asp-page="/Account/Login">Login</a>
Tip You can reference Razor Pages in the Identity area by setting the area route value to Identity. You can use the asp-area attribute in Tag Helpers that generate links.
提示:可以通过将区域路由值设置为 Identity 来引用 Identity 区域中的 Razor Pages。您可以在生成链接的标记帮助程序中使用 asp-area 属性。
In addition to viewing the new files included thanks to ASP.NET Core Identity, open Program.cs and look at the changes there. The most obvious change is the additional configuration, which adds all the services Identity requires, as shown in the following listing.
除了查看 ASP.NET Core Identity 包含的新文件外,还可以打开 Program.cs 并查看其中的更改。最明显的变化是额外的配置,它添加了 Identity 所需的所有服务,如下面的清单所示。
Listing 23.1 Adding ASP.NET Core Identity services to ConfigureServices
清单 23.1 向 ConfigureServices 添加 ASP.NET Core Identity 服务
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
string connectionString = builder.Configuration #A
.GetConnectionString("DefaultConnection"); #A
builder.Services.AddDbContext<ApplicationDbContext>(options => #A
options.UseSqlServer(connectionString)); #A
builder.Services.AddDatabaseDeveloperPageExceptionFilter(); #B
builder.Services.AddDefaultIdentity<IdentityUser>(options => #C
options.SignIn.RequireConfirmedAccount = true) #D
.AddEntityFrameworkStores<ApplicationDbContext>(); #E
builder.Services.AddRazorPages();
// remaining configuration not show
❶ ASP.NET Core Identity uses EF Core, so it includes the standard EF Core configuration.
ASP.NET Core Identity 使用 EF Core,因此它包括标准 EF Core 配置。
❷ Adds optional database services to enhance the DeveloperExceptionPage
添加可选的数据库服务以增强 DeveloperExceptionPage
❸ Adds the Identity system, including the default UI, and configures the user type as IdentityUser
添加标识系统,包括默认 UI,并将用户类型配置为 IdentityUser
❹ Requires users to confirm their accounts (typically by email) before they log in
要求用户在登录前确认其帐户(通常通过电子邮件)
❺ Configures Identity to store its data in EF Core
配置标识以将其数据存储在 EF Core 中
The AddDefaultIdentity() extension method does several things:
AddDefaultIdentity() 扩展方法执行以下几项作:
• Adds the core ASP.NET Core Identity services.
添加核心 ASP.NET 核心身份服务。
• Configures the application user type to be IdentityUser. This is the entity model that is stored in the database and represents a “user” in your application. You can extend this type if you need to, but that’s not always necessary, as you’ll see in section 23.6.
将应用程序用户类型配置为 IdentityUser。这是存储在数据库中的实体模型,表示应用程序中的 “用户”。如果需要,您可以扩展此类型,但这并不总是必要的,如第 23.6 节所示。
• Adds the default UI Razor Pages for registering, logging in, and managing users.
添加用于注册、登录和管理用户的默认 UI Razor Pages。
• Configures token providers for generating MFA and email confirmation tokens.
配置用于生成 MFA 和电子邮件确认令牌的令牌提供程序。
Where is the authentication middleware?
身份验证中间件在哪里?If you’re already familiar with previous versions of ASP.NET Core, you might be surprised to notice the lack of any authentication middleware in the default template. Given everything you’ve learned about how authentication works, that should be surprising!
如果您已经熟悉 ASP.NET Core 的早期版本,您可能会惊讶地注意到默认模板中缺少任何身份验证中间件。鉴于您学到的有关身份验证工作原理的所有信息,这应该令人惊讶!The answer to this riddle is that the authentication middleware is in the pipeline, even though you can’t see it. As I discussed in chapter 4, WebApplication automatically adds many middleware components to the pipeline for you, including the routing middleware, the endpoint middleware, and—yes—the authentication middleware. So the reason you don’t see it in the pipeline is that it’s already been added.
这个谜题的答案是,身份验证中间件正在开发中,即使您看不到它。正如我在第 4 章中所讨论的,WebApplication 会自动将许多中间件组件添加到管道中,包括路由中间件、端点中间件,是的,还有身份验证中间件。因此,您在管道中没有看到它的原因是它已被添加。In fact, WebApplication also automatically adds the authorization middleware to the pipeline, but in this case the template still calls UseAuthorization(). Why? For the same reason that the template also calls UseRouting(): to control exactly where in the pipeline the middleware is added.
事实上,WebApplication 还会自动将授权中间件添加到管道中,但在这种情况下,模板仍然调用 UseAuthorization()。为什么?出于与模板还调用 UseRouting() 相同的原因:以准确控制中间件在管道中的添加位置。As I mentioned in chapter 4, you can override the automatically added middleware by adding it yourself manually. It’s crucial that the authorization middleware be placed after the routing middleware, and as mentioned in chapter 4, you typically want to place your routing middleware after the static file middleware. As the routing middleware needs to move, so does the authorization middleware!
正如我在第 4 章中提到的,你可以通过自己手动添加来覆盖自动添加的中间件。将授权中间件放在路由中间件之后至关重要,如第 4 章所述,您通常希望将路由中间件放在静态文件中间件之后。由于路由中间件需要移动,授权中间件也需要移动!Traditionally, the authentication middleware is also placed after the routing middleware, before the authorization middleware, but this isn’t crucial. The only requirement is that it’s placed before any middleware that requires an authenticated user, such as the authorization middleware.
传统上,身份验证中间件也放在路由中间件之后,授权中间件之前,但这并不重要。唯一的要求是,它位于任何需要经过身份验证的用户的中间件(例如授权中间件)之前。
》If you wish, you can move the location of the authentication middleware by calling UseAuthentication() at the appropriate point. I prefer to limit the work done on requests where possible, so I typically take this approach, moving it between the call to UseRouting() and UseAuthorization():
如果需要,可以通过在适当的位置调用 UseAuthentication() 来移动身份验证中间件的位置。我更喜欢尽可能限制对请求所做的工作,因此我通常采用这种方法,在对 UseRouting() 和 UseAuthorization() 的调用之间移动它:
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
If you don’t place the authentication middleware at the correct point in the pipeline, you can run into strange bugs where users aren’t authenticated correctly or authorization policies aren’t applied correctly. The templates work out of the box, but you need to take care if you’re working with an existing application or moving middleware around.
如果您没有将身份验证中间件放置在管道中的正确位置,则可能会遇到奇怪的错误,即用户未正确进行身份验证或授权策略未正确应用。这些模板开箱即用,但如果您正在使用现有应用程序或移动中间件,则需要小心。
Now that you’ve got an overview of the additions made by Identity, we’ll look in a bit more detail at the database schema and how Identity stores users in the database.
现在,您已经大致了解了 Identity 所做的添加,我们将更详细地了解数据库架构以及 Identity 如何在数据库中存储用户。
23.3.3 The ASP.NET Core Identity data model
23.3.3 ASP.NET Core Identity 数据模型
Out of the box, and in the default templates, Identity uses EF Core to store user accounts. It provides a base DbContext that you can inherit from, called IdentityDbContext, which uses an IdentityUser as the user entity for your application.
在默认模板中,Identity 使用 EF Core 来存储用户帐户。它提供了一个可以从中继承的基本 DbContext,称为 IdentityDbContext,它使用 IdentityUser 作为应用程序的用户实体。
In the template, the app’s DbContext is called ApplicationDbContext. If you open this file, you’ll see it’s sparse; it inherits from the IdentityDbContext base class I described earlier, and that’s it. What does this base class give you? The easiest way to see is to update a database with the migrations and take a look.
在模板中,应用的 DbContext 称为 ApplicationDbContext。如果打开此文件,您将看到它是稀疏的;它继承自我前面介绍的 IdentityDbContext 基类,仅此而已。这个基类为您提供了什么?最简单的方法是使用迁移更新数据库并查看。
Applying the migrations is the same process as in chapter 12. Ensure that the connection string points to where you want to create the database, open a command prompt in your project folder, and run this command to update the database with the migrations:
应用迁移的过程与第 12 章中的过程相同。确保连接字符串指向要创建数据库的位置,在项目文件夹中打开命令提示符,然后运行以下命令以使用迁移更新数据库:
dotnet ef database update
Tip If you see an error after running the dotnet ef command, ensure that you have the .NET tool installed by following the instructions provided in section 12.3.1. Also make sure that you run the command from the project folder, not the solution folder.
提示:如果在运行 dotnet ef 命令后看到错误,请确保按照第 12.3.1 节中提供的说明安装了 .NET 工具。此外,请确保从项目文件夹(而不是解决方案文件夹)运行命令。
If the database doesn’t exist, the command-line interface (CLI) creates it. Figure 23.7 shows what the database looks like for the default template.
如果数据库不存在,则命令行界面 (CLI) 会创建该数据库。图 23.7 显示了默认模板的数据库外观。
Tip If you’re using MS SQL Server (or LocalDB), you can use the SQL Server Object Explorer in Visual Studio to browse tables and objects in your database. See Microsoft’s “How to: Connect to a Database and Browse Existing Objects” article for details: http://mng.bz/mg8r.
提示:如果您使用的是 MS SQL Server(或 LocalDB),则可以使用 Visual Studio 中的 SQL Server 对象资源管理器浏览数据库中的表和对象。有关详细信息,请参阅 Microsoft 的“如何:连接到数据库并浏览现有对象”一文:http://mng.bz/mg8r。
Figure 23.7 The database schema used by ASP.NET Core Identity
图 23.7 ASP.NET Core Identity 使用的数据库架构
That’s a lot of tables! You shouldn’t need to interact with these tables directly (Identity handles that for you), but it doesn’t hurt to have a basic grasp of what they’re for:
好多表啊!您不需要直接与这些表交互(Identity 会为您处理),但对它们的用途有基本的了解并没有什么坏处:
• EFMigrationsHistory—The standard EF Core migrations table that records which migrations have been applied.
EFMigrationsHistory - 标准 EF Core 迁移表,用于记录已应用的迁移。
• AspNetUsers—The user profile table itself. This is where IdentityUser is serialized to. We’ll take a closer look at this table shortly.
AspNetUsers — 用户配置文件表本身。这是 IdentityUser 序列化到的位置。我们稍后会仔细看看这个表格。
• AspNetUserClaims—The claims associated with a given user. A user can have many claims, so it’s modeled as a many-to-one relationship.
AspNetUserClaims - 与给定用户关联的声明。一个用户可以有多个声明,因此它被建模为多对一关系。
• AspNetUserLogins and AspNetUserTokens—These are related to third-party logins. When configured, these let users sign in with a Google or Facebook account (for example) instead of creating a password on your app.
AspNetUserLogins 和 AspNetUserTokens - 这些与第三方登录相关。配置后,这些表允许用户使用 Google 或 Facebook 帐户 (例如) 登录,而不是在您的应用程序上创建密码。
• AspNetUserRoles, AspNetRoles, and AspNetRoleClaims—These tables are somewhat of a legacy left over from the old role-based permission model of the pre-.NET 4.5 days, instead of the claims-based permission model. These tables let you define roles that multiple users can belong to. Each role can be assigned multiple claims. These claims are effectively inherited by a user principal when they are assigned that role.
AspNetUserRoles、AspNetRoles 和 AspNetRoleClaims - 这些表在某种程度上是 pre-.NET 4.5 天的基于角色的旧权限模型遗留下来的遗留问题,而不是基于声明的权限模型。这些表允许您定义多个用户可以属于的角色。每个角色都可以分配多个声明。当为用户主体分配该角色时,这些声明将由用户主体有效地继承。
You can explore these tables yourself, but the most interesting of them is the AspNetUsers table, shown in figure 23.8.
您可以自己浏览这些表,但其中最有趣的是 AspNetUsers 表,如图 23.8 所示。
Figure 23.8 The AspNetUsers table is used to store all the details required to authenticate a user.
图 23.8 AspNetUsers 表用于存储验证用户所需的所有详细信息。
Most of the columns in the AspNetUsers table are security-related—the user’s email, password hash, whether they have confirmed their email, whether they have MFA enabled, and so on. By default, there are no columns for additional information, like the user’s name.
AspNetUsers 表中的大多数列都与安全相关 — 用户的电子邮件、密码哈希、他们是否已确认其电子邮件、他们是否启用了 MFA 等。默认情况下,没有其他信息(如用户名)的列。
NOTE You can see from figure 23.8 that the primary key Id is stored as a string column. By default, Identity uses Guid for the identifier. To customize the data type, see the “Change the primary key type” section of Microsoft’s “Identity model customization in ASP.NET Core” documentation: http://mng.bz/5jdB.
注意:从图 23.8 中可以看出,主键 Id 存储为字符串列。默认情况下,Identity 使用 Guid 作为标识符。要自定义数据类型,请参阅 Microsoft 的“ASP.NET Core 中的身份模型自定义”文档的“更改主键类型”部分:http://mng.bz/5jdB。
Any additional properties of the user are stored as claims in the AspNetUserClaims table associated with that user. This lets you add arbitrary additional information without having to change the database schema to accommodate it. Want to store the user’s date of birth? You could add a claim to that user; there’s no need to change the database schema. You’ll see this in action in section 23.6, when you add a Name claim to every new user.
用户的任何其他属性都作为声明存储在与该用户关联的 AspNetUserClaims 表中。这样,您就可以添加任意的附加信息,而不必更改数据库架构来容纳它。想要存储用户的出生日期?您可以向该用户添加声明;无需更改数据库架构。您将在第 23.6 节中看到这一点,当您为每个新用户添加 Name 声明时。
NOTE Adding claims is often the easiest way to extend the default IdentityUser, but you can add properties to the IdentityUser directly. This requires database changes but is nevertheless useful in many situations. You can read how to add custom data using this approach here: http://mng.bz/Xd61.
注意:添加声明通常是扩展默认 IdentityUser 的最简单方法,但您可以直接向 IdentityUser 添加属性。这需要更改数据库,但在许多情况下仍然很有用。您可以在此处阅读如何使用此方法添加自定义数据:http://mng.bz/Xd61。
It’s important to understand the difference between the IdentityUser entity (stored in the AspNetUsers table) and the ClaimsPrincipal, which is exposed on HttpContext.User. When a user first logs in, an IdentityUser is loaded from the database. This entity is combined with additional claims for the user from the AspNetUserClaims table to create a ClaimsPrincipal. It’s this ClaimsPrincipal that is used for authentication and is serialized to the authentication cookie, not the IdentityUser.
了解 IdentityUser 实体(存储在 AspNetUsers 表中)和 ClaimsPrincipal(在 HttpContext.User 上公开)之间的区别非常重要。当用户首次登录时,将从数据库中加载 IdentityUser。此实体与 AspNetUserClaims 表中用户的其他声明组合在一起,以创建 ClaimsPrincipal。此 ClaimsPrincipal 用于身份验证,并序列化为身份验证 Cookie,而不是 IdentityUser。
It’s useful to have a mental model of the underlying database schema Identity uses, but in day-to-day work, you shouldn’t have to interact with it directly. That’s what Identity is for, after all! In the next section we’ll look at the other end of the scale: the UI of the app and what you get out of the box with the default UI.
拥有 Identity 使用的基础数据库架构的心智模型很有用,但在日常工作中,您不应该直接与之交互。毕竟,这就是 Identity 的意义所在!在下一节中,我们将了解天平的另一端:应用程序的 UI 以及您使用默认 UI 开箱即用的功能。
23.3.4 Interacting with ASP.NET Core Identity
23.3.4 与 ASP.NET Core Identity 交互
You’ll want to explore the default UI yourself to get a feel for how the pieces fit together, but in this section I’ll highlight what you get out of the box, as well as areas that typically require additional attention right away.
您需要亲自探索默认 UI,以了解各个部分是如何组合在一起的,但在本节中,我将重点介绍您开箱即用的功能,以及通常需要立即额外注意的领域。
The entry point to the default UI is the user registration page of the application, shown in figure 23.9. The register page enables users to sign up to your application by creating a new IdentityUser with an email and a password. After creating an account, users are redirected to a screen indicating that they should confirm their email. No email service is enabled by default, as this is dependent on your configuring an external email service. You can read how to enable email sending in Microsoft’s “Account confirmation and password recovery in ASP.NET Core” documentation at http://mng.bz/6gBo. Once you configure this, users will automatically receive an email with a link to confirm their account.
默认 UI 的入口点是应用程序的用户注册页面,如图 23.9 所示。通过注册页面,用户可以通过使用电子邮件和密码创建新的 IdentityUser 来注册您的应用程序。创建账户后,用户将被重定向到一个屏幕,指示他们应该确认他们的电子邮件。默认情况下,不启用任何电子邮件服务,因为这取决于您配置外部电子邮件服务。您可以在 http://mng.bz/6gBo 的 Microsoft 的“ASP.NET Core 中的帐户确认和密码恢复”文档中阅读如何启用电子邮件发送。配置此项后,用户将自动收到一封电子邮件,其中包含用于确认其帐户的链接。
Figure 23.9 The registration flow for users using the default Identity UI. Users enter an email and password and are redirected to a “confirm your email” page. This is a placeholder page by default, but if you enable email confirmation, this page will update appropriately.
图 23.9 使用默认 Identity UI 的用户的注册流程。用户输入电子邮件和密码,并被重定向到“确认您的电子邮件”页面。默认情况下,这是一个占位符页面,但如果您启用电子邮件确认,此页面将相应地更新。
By default, user emails must be unique (you can’t have two users with the same email), and the password must meet various length and complexity requirements. You can customize these options and more in the configuration lambda of the call to AddDefaultIdentity() in Program.cs, as shown in the following listing.
默认情况下,用户电子邮件必须是唯一的(您不能让两个用户使用同一电子邮件),并且密码必须满足各种长度和复杂性要求。您可以在 Program.cs 中调用 AddDefaultIdentity() 的配置 lambda 中自定义这些选项以及更多选项,如下面的清单所示。
Listing 23.2 Customizing Identity settings in ConfigureServices in Startup.cs
清单 23.2 在 Startup.cs 的 ConfigureServices 中自定义身份设置
builder.Services.AddDefaultIdentity<IdentityUser>(options =>
{
options.SignIn.RequireConfirmedAccount = true; #A
options.Lockout.AllowedForNewUsers = true; #B
options.Password.RequiredLength = 12; #C
options.Password.RequireNonAlphanumeric = false; #C
options.Password.RequireDigit = false; #C
})
.AddEntityFrameworkStores<AppDbContext>();
❶ Requires users to confirm their account by email before they can log in
要求用户在登录之前通过电子邮件确认其帐户
❷ Enables user lockout, to prevent brute-force attacks against user passwords
启用用户锁定,以防止对用户密码的暴力攻击
❸ Updates password requirements. Current guidance is to require long passwords.
更新密码要求。当前的指导是要求使用长密码。
After a user has registered with your application, they need to log in, as shown in figure 23.10. On the right side of the login page, the default UI templates describe how you, the developer, can configure external login providers, such as Facebook and Google. This is useful information for you, but it’s one of the reasons you may need to customize the default UI templates, as you’ll see in section 23.5.
用户注册到您的应用程序后,他们需要登录,如图 23.10 所示。在登录页面的右侧,默认 UI 模板描述了您(开发人员)如何配置外部登录提供程序,例如 Facebook 和 Google。这对您有用,但这也是您可能需要自定义默认 UI 模板的原因之一,如第 23.5 节所示。
Figure 23.10 Logging in with an existing user and managing the user account. The Login page describes how to configure external login providers, such as Facebook and Google. The user-management pages allow users to change their email and password and to configure MFA.
图 23.10 使用现有用户登录并管理用户帐户。Login (登录) 页面介绍了如何配置外部登录提供程序,例如 Facebook 和 Google。用户管理页面允许用户更改其电子邮件和密码以及配置 MFA。
Once a user has signed in, they can access the management pages of the identity UI. These allow users to change their email, change their password, configure MFA with an authenticator app, or delete all their personal data. Most of these functions work without any effort on your part, assuming that you’ve already configured an email-sending service.
用户登录后,他们可以访问身份 UI 的管理页面。这些允许用户更改他们的电子邮件、更改他们的密码、使用身份验证器应用程序配置 MFA 或删除他们的所有个人数据。这些函数中的大多数都无需您执行任何作即可工作,前提是您已经配置了电子邮件发送服务。
That covers everything you get in the default UI templates. It may seem somewhat minimal, but it covers a lot of the requirements that are common to almost all apps. Nevertheless, there are a few things you’ll nearly always want to customize:
这涵盖了您在默认 UI 模板中获得的所有内容。它可能看起来有些微不足道,但它涵盖了几乎所有应用程序通用的许多要求。不过,您几乎总是需要自定义一些内容:
• Configure an email-sending service, to enable account confirmation and password recovery, as described in Microsoft’s “Account confirmation and password recovery in ASP.NET Core” documentation: http://mng.bz/vzy7.
配置电子邮件发送服务,以启用帐户确认和密码恢复,如 Microsoft 的“ASP.NET Core 中的帐户确认和密码恢复”文档中所述:http://mng.bz/vzy7。
• Add a QR code generator for the enable MFA page, as described in Microsoft’s “Enable QR Code generation for TOTP authenticator apps in ASP.NET Core” documentation: http://mng.bz/4Zmw.
为启用 MFA 页面添加 QR 码生成器,如 Microsoft 的“在 ASP.NET Core 中为 TOTP 验证器应用程序启用 QR 码生成”文档中所述:http://mng.bz/4Zmw。
• Customize the register and login pages to remove the documentation link for enabling external services. You’ll see how to do this in section 23.5. Alternatively, you may want to disable user registration entirely, as described in Microsoft’s “Scaffold Identity in ASP.NET Core projects” documentation: http://mng.bz/QmMG.
自定义注册和登录页面,以删除用于启用外部服务的文档链接。您将在 Section 23.5 中看到如何执行此作。或者,您可能希望完全禁用用户注册,如 Microsoft 的“ASP.NET Core 项目中的基架身份”文档中所述:http://mng.bz/QmMG。
• Collect additional information about users on the registration page. You’ll see how to do this in section 23.6.
在注册页面上收集有关用户的其他信息。您将在 Section 23.6 中看到如何执行此作。
There are many more ways you can extend or update the Identity system and lots of options available, so I encourage you to explore Microsoft’s “Overview of ASP.NET Core authentication” at http://mng.bz/XdGv to see your options. In the next section you’ll see how to achieve another common requirement: adding users to an existing application.
还有更多方法可以扩展或更新 Identity 系统,并且有很多可用选项,因此我鼓励您在 http://mng.bz/XdGv 上浏览 Microsoft 的“ASP.NET Core 身份验证概述”以查看您的选项。在下一节中,您将了解如何实现另一个常见要求:将用户添加到现有应用程序。
23.4 Adding ASP.NET Core Identity to an existing project
23.4 将 ASP.NET Core Identity 添加到现有工程
In this section we’re going to add users to an existing application. The initial app is a Razor Pages app, based on recipe application from chapter 12. This is a working app that you want to add user functionality to. In chapter 24 we’ll extend this work to restrict control regarding who’s allowed to edit recipes on the app.
在本节中,我们将向现有应用程序添加用户。初始应用是 Razor Pages 应用,基于第 12 章中的配方应用。这是您要向其添加用户功能的工作应用程序。在第 24 章中,我们将扩展这项工作,以限制对谁可以在应用程序上编辑配方的控制。
By the end of this section, you’ll have an application with a registration page, a login screen, and a manage account screen, like the default templates. You’ll also have a persistent widget in the top right of the screen showing the login status of the current user, as shown in figure 23.11.
在本部分结束时,您将拥有一个应用程序,其中包含注册页面、登录屏幕和管理帐户屏幕,就像默认模板一样。屏幕右上角还有一个持久小部件,显示当前用户的登录状态,如图 23.11 所示。
Figure 23.11 The recipe app after adding authentication, showing the login widget
图 23.11 添加身份验证后的配方应用程序,显示登录小部件
As in section 23.3, I’m not going to customize any of the defaults at this point, so we won’t set up external login providers, email confirmation, or MFA. I’m concerned only with adding ASP.NET Core Identity to an existing app that’s already using EF Core.
与 Section 23.3 一样,我此时不打算自定义任何默认值,因此我们不会设置外部登录提供程序、电子邮件确认或 MFA。我只关心将 ASP.NET Core Identity 添加到已在使用 EF Core 的现有应用程序。
Tip It’s worth making sure you’re comfortable with the new project templates before you go about adding Identity to an existing project. Create a test app, and consider setting up an external login provider, configuring an email provider, and enabling MFA. This will take a bit of time, but it’ll be invaluable for deciphering errors when you come to adding Identity to existing apps.
提示在将 Identity 添加到现有项目之前,值得确保您熟悉新的项目模板。创建测试应用程序,并考虑设置外部登录提供程序、配置电子邮件提供程序并启用 MFA。这将花费一些时间,但在您将 Identity 添加到现有应用程序时,它对于破译错误非常宝贵。
To add Identity to your app, you’ll need to do the following:
要将 Identity 添加到您的应用程序,您需要执行以下作:
- Add the ASP.NET Core Identity NuGet packages.
添加 ASP.NET Core Identity NuGet 包。 - Add the required Identity services to the dependency injection (DI) container.
将所需的身份服务添加到依赖关系注入 (DI) 容器中。 - Update the EF Core data model with the Identity entities.
使用 Identity 实体更新 EF Core 数据模型。 - Update your Razor Pages and layouts to provide links to the Identity UI.
更新 Razor 页面和布局,以提供指向标识 UI 的链接。
This section tackles each of these steps in turn. At the end of section 23.4 you’ll have successfully added user accounts to the recipe app.
本节将依次介绍这些步骤中的每一个。在第 23.4 节结束时,您将成功地将用户帐户添加到配方应用程序。
23.4.1 Configuring the ASP.NET Core Identity services
23.4.1 配置 ASP.NET Core Identity 服务
You can add ASP.NET Core Identity with the default UI to an existing app by referencing two NuGet packages:
您可以通过引用两个 NuGet 包,将带有默认 UI 的 ASP.NET Core Identity 添加到现有应用程序:
• Microsoft.AspNetCore.Identity.EntityFrameworkCore—Provides all the core Identity services and integration with EF Core
Microsoft.AspNetCore.Identity.EntityFrameworkCore - 提供所有核心身份服务以及与 EF Core的集成
• Microsoft.AspNetCore.Identity.UI—Provides the default UI Razor Pages
Microsoft.AspNetCore.Identity.UI - 提供默认 UI Razor 页面
Update your project .csproj file to include these two packages:
更新项目 .csproj 文件以包含以下两个包:
<PackageReference
Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore"
Version="7.0.0" />
<PackageReference
Include="Microsoft.AspNetCore.Identity.UI" Version="7.0.0" />
These packages bring in all the additional required dependencies you need to add Identity with the default UI. Be sure to run dotnet restore after adding them to your project.
这些包引入了使用默认 UI 添加 Identity 所需的所有额外必需依赖项。请务必在将它们添加到项目后运行 dotnet restore。
Once you’ve added the Identity packages, you can update your Program.cs file to include the Identity services, as shown in the following listing. This is similar to the default template setup you saw in listing 23.1, but make sure to reference your existing AppDbContext.
添加 Identity 包后,您可以更新 Program.cs 文件以包含 Identity 服务,如以下清单所示。这类似于您在清单 23.1 中看到的默认模板设置,但请确保引用您现有的 AppDbContext。
Listing 23.3 Adding ASP.NET Core Identity services to the recipe app
清单 23.3 将 ASP.NET Core Identity 服务添加到 recipe 应用程序
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<AppDbContext>(options => #A
options.UseSqlite(builder.Configuration #A
.GetConnectionString("DefaultConnection")!)); #A
builder.Services.AddDefaultIdentity<ApplicationUser>(options => #B
options.SignIn.RequireConfirmedAccount = true) #B
.AddEntityFrameworkStores<AppDbContext>(); #C
builder.Services.AddRazorPages();
builder.Services.AddScoped<RecipeService>();
❶ The existing service configuration is unchanged.
现有服务配置保持不变。
❷ Adds the Identity services to the DI container and uses a custom user type, ApplicationUser
将身份服务添加到 DI 容器并使用自定义用户类型 ApplicationUser
❸ Makes sure you use the name of your existing DbContext app
确保您使用现有 DbContext 应用程序的名称
This adds all the necessary services and configures Identity to use EF Core. I’ve introduced a new type here, ApplicationUser, which we’ll use to customize our user entity later. You’ll see how to add this type in section 23.4.2.
这将添加所有必要的服务,并将 Identity 配置为使用 EF Core。我在这里引入了一个新类型 ApplicationUser,我们稍后将使用它来自定义我们的用户实体。您将在 Section 23.4.2 中看到如何添加此类型。
The next step is optional: add the AuthenticationMiddleware after the call to UseRouting() on WebApplication, as shown in the following listing. As I mentioned previously, the authentication middleware is added automatically by WebApplication, so this step is optional. I prefer to delay authentication until after the call to UseRouting(), as it eliminates the need to perform unnecessary work decrypting the authentication cookie for requests that don’t reach the routing middleware, such as requests for static files.
下一步是可选的:在调用 WebApplication 上的 UseRouting() 之后添加 AuthenticationMiddleware,如下面的清单所示。正如我前面提到的,身份验证中间件是由 WebApplication 自动添加的,因此此步骤是可选的。我更喜欢将身份验证延迟到调用 UseRouting() 之后,因为这样就无需为未到达路由中间件的请求(例如静态文件请求)执行不必要的身份验证 Cookie 解密工作。
Listing 23.4 Adding AuthenticationMiddleware to the recipe app
列表 23.4 将 AuthenticationMiddleware 添加到 recipe 应用程序
app.UseStaticFiles(); #A
app.UseRouting();
app.UseAuthentication(); #B
app.UseAuthorization(); #C
app.MapRazorPages();
app.Run
❶ StaticFileMiddleware will never see requests as authenticated, even after you sign in.
StaticFileMiddleware 永远不会将请求视为已验证,即使在你登录后也是如此。
❷ Adds AuthenticationMiddleware after UseRouting() and before UseAuthorization
在 UseRouting() 之后和 UseAuthorization之前添加 AuthenticationMiddleware
❸ Middleware after AuthenticationMiddleware can read the user principal from HttpContext.User.
AuthenticationMiddleware 之后的中间件可以从 HttpContext.User 读取用户主体。
You’ve configured your app to use Identity, so the next step is updating EF Core’s data model. You’re already using EF Core in this app, so you need to update your database schema to include the tables that Identity requires.
你已将应用配置为使用 Identity,因此下一步是更新 EF Core 的数据模型。你已在此应用中使用 EF Core,因此需要更新数据库架构以包含 Identity 所需的表。
23.4.2 Updating the EF Core data model to support Identity
23.4.2 更新 EF Core 数据模型以支持身份
The code in listing 23.3 won’t compile, as it references the ApplicationUser type, which doesn’t yet exist. Create the ApplicationUser in the Data folder, using the following line:
清单 23.3 中的代码无法编译,因为它引用了尚不存在的 ApplicationUser 类型。使用以下行在 Data 文件夹中创建 ApplicationUser:
public class ApplicationUser : IdentityUser { }
It’s not strictly necessary to create a custom user type in this case (for example, the default templates use the raw IdentityUser), but I find it’s easier to add the derived type now rather than try to retrofit it later if you need to add extra properties to your user type.
在这种情况下,并非绝对需要创建自定义用户类型(例如,默认模板使用原始 IdentityUser),但我发现,如果您需要向用户类型添加额外的属性,现在添加派生类型比以后尝试修改它更容易。
In section 23.3.3 you saw that Identity provides a DbContext called IdentityDbContext, which you can inherit from. The IdentityDbContext base class includes the necessary DbSet
在第 23.3.3 节中,您看到 Identity 提供了一个名为 IdentityDbContext 的 DbContext,您可以从中继承。IdentityDbContext 基类包括使用 EF Core 存储用户实体所需的 DbSet。
Updating an existing DbContext for Identity is simple: update your app’s DbContext to inherit from IdentityDbContext (which itself inherits from DbContext), as shown in the following listing. We’re using the generic version of the base Identity context in this case and providing the ApplicationUser type.
更新现有的 DbContext for Identity 很简单:更新应用程序的 DbContext 以从 IdentityDbContext(它本身继承自 DbContext)继承,如下面的清单所示。在本例中,我们使用基本 Identity 上下文的通用版本,并提供 ApplicationUser 类型。
Listing 23.5 Updating AppDbContext to use IdentityDbContext
列表 23.5 更新 AppDbContext 以使用 IdentityDbContext
public class AppDbContext : IdentityDbContext<ApplicationUser> #A
{
public AppDbContext(DbContextOptions<AppDbContext> options) #B
: base(options) #B
{ } #B
public DbSet<Recipe> Recipes { get; set; } #B
}
❶ Updates to inherit from the Identity context instead of directly from DbContext
更新以从 Identity 上下文继承,而不是直接从 DbContext继承
❷ The remainder of the class remains the same.
类的其余部分保持不变。
Effectively, by updating the base class of your context in this way, you’ve added a whole load of new entities to EF Core’s data model. As you saw in chapter 12, whenever EF Core’s data model changes, you need to create a new migration and apply those changes to the database.
实际上,通过以这种方式更新上下文的基类,你已向 EF Core 的数据模型添加了大量新实体。如第 12 章所示,每当 EF Core 的数据模型发生更改时,都需要创建新的迁移并将这些更改应用于数据库。
At this point, your app should compile, so you can add a new migration called AddIdentitySchema using
此时,你的应用应该会编译,因此你可以使用 AddIdentitySchema 添加名为 AddIdentitySchema 的新迁移
dotnet ef migrations add AddIdentitySchema
The final step is updating your application’s Razor Pages and layouts to reference the default identity UI. Normally, adding 30 new Razor Pages to your application would be a lot of work, but using the default Identity UI makes it a breeze.
最后一步是更新应用程序的 Razor Pages 和布局以引用默认标识 UI。通常,向应用程序添加 30 个新的 Razor 页面会是一项艰巨的工作,但使用默认标识 UI 会变得轻而易举。
23.4.3 Updating the Razor views to link to the Identity UI
23.4.3 更新 Razor 视图以链接到身份 UI
Technically, you don’t have to update your Razor Pages to reference the pages included in the default UI, but you probably want to add the login widget to your app’s layout at a minimum. You’ll also want to make sure that your Identity Razor Pages use the same base Layout.cshtml as the rest of your application.
从技术上讲,您不必更新 Razor Pages 来引用默认 UI 中包含的页面,但您可能希望至少将登录小组件添加到应用程序的布局中。还需要确保 Identity Razor 页面使用与应用程序其余部分相同的基 Layout.cshtml。
We’ll start by fixing the layout for your Identity pages. Create a file at the “magic” path Areas/Identity/Pages/_ViewStart.cshtml, and add the following contents:
首先,我们将修复 Identity 页面的布局。在“magic”路径 Areas/Identity/Pages/_ViewStart.cshtml 处创建一个文件,并添加以下内容:
@{ Layout = "/Pages/Shared/_Layout.cshtml"; }
This sets the default layout for your Identity pages to your application’s default layout. Next, add a _LoginPartial.cshtml file in Pages/Shared to define the login widget, as shown in the following listing. This is pretty much identical to the template generated by the default template, but it uses our custom ApplicationUser instead of the default IdentityUser.
这会将 Identity 页面的默认布局设置为应用程序的默认布局。接下来,在 Pages/Shared 中添加 _LoginPartial.cshtml 文件以定义登录小组件,如下面的清单所示。这与默认模板生成的模板几乎相同,但它使用我们的自定义 ApplicationUser 而不是默认的 IdentityUser。
Listing 23.6 Adding a _LoginPartial.cshtml to an existing app
列表 23.6 将 _LoginPartial.cshtml 添加到现有应用程序
@using Microsoft.AspNetCore.Identity
@using RecipeApplication.Data; #A
@inject SignInManager<ApplicationUser> SignInManager #B
@inject UserManager<ApplicationUser> UserManager #B
<ul class="navbar-nav">
@if (SignInManager.IsSignedIn(User))
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Identity"
asp-page="/Account/Manage/Index" title="Manage">
Hello @User.Identity.Name!</a>
</li>
<li class="nav-item">
<form class="form-inline" asp-page="/Account/Logout"
asp-route-returnUrl="@Url.Page("/", new { area = "" })"
asp-area="Identity" method="post" >
<button class="nav-link btn btn-link text-dark"
type="submit">Logout</button>
</form>
</li>
}
else
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Identity"
asp-page="/Account/Register">Register</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Identity"
asp-page="/Account/Login">Login</a>
</li>
}
</ul>
❶ Updates to your project’s namespace that contains ApplicationUser
更新包含 ApplicationUser的项目命名空间
❷ The default template uses IdentityUser. Update to use ApplicationUser instead.
默认模板使用 IdentityUser。更新以改用 ApplicationUser。
This partial shows the current login status of the user and provides links to register or sign in. All that remains is to render the partial by calling
此部分显示用户的当前登录状态,并提供用于注册或登录的链接。剩下的就是通过调用
<partial name="_LoginPartial" />
in the main layout file of your app, _Layout.cshtml.
在应用程序的主布局文件中,_Layout.cshtml。
And there you have it: you’ve added Identity to an existing application. The default UI makes doing this relatively simple, and you can be sure you haven’t introduced any security holes by building your own UI!
好了:您已将 Identity 添加到现有应用程序。默认 UI 使此作相对简单,您可以确保通过构建自己的 UI 没有引入任何安全漏洞!
As I described in section 23.3.4, there are some features that the default UI doesn’t provide and you need to implement yourself, such as email confirmation and MFA QR code generation. It’s also common to find that you want to update a single page here and there. In the next section I’ll show how you can replace a page in the default UI, without having to rebuild the entire UI yourself.
正如我在第 23.3.4 节中所描述的,默认 UI 不提供一些功能,您需要自行实现,例如电子邮件确认和 MFA QR 码生成。你也经常会发现你想在这里和那里更新单个页面。在下一节中,我将展示如何替换默认 UI 中的页面,而不必自己重新构建整个 UI。
23.5 Customizing a page in ASP.NET Core Identity’s default UI
23.5 在 ASP.NET Core Identity 的默认 UI 中自定义页面
In this section you’ll learn how to use scaffolding to replace individual pages in the default Identity UI. You’ll learn to scaffold a page so that it overrides the default UI, allowing you to customize both the Razor template and the PageModel page handlers.
在本节中,您将学习如何使用基架替换默认 Identity UI 中的各个页面。您将学习如何搭建页面基架,使其覆盖默认 UI,从而允许您自定义 Razor 模板和 PageModel 页面处理程序。
Having Identity provide the whole UI for your application is great in theory, but in practice there are a few wrinkles, as you saw in section 23.3.4. The default UI provides as much as it can, but there are some things you may want to tweak. For example, both the login and register pages describe how to configure external login providers for your ASP.NET Core applications, as you saw in figures 23.12 and 23.13. That’s useful information for you as a developer, but it’s not something you want to be showing to your users. Another often-cited requirement is the desire to change the look and feel of one or more pages.
让 Identity 为您的应用程序提供整个 UI 在理论上很好,但在实践中存在一些问题,正如您在第 23.3.4 节中看到的那样。默认 UI 提供了尽可能多的功能,但您可能需要调整一些内容。例如,登录和注册页面都描述了如何为 ASP.NET Core 应用程序配置外部登录提供程序,如图 23.12 和 23.13 所示。这对开发人员来说很有用,但不是您希望向用户展示的信息。另一个经常被引用的要求是希望更改一个或多个页面的外观。
Luckily, the default Identity UI is designed to be incrementally replaceable, so you can override a single page without having to rebuild the entire UI yourself. On top of that, both Visual Studio and the .NET CLI have functions that allow you to scaffold any (or all) of the pages in the default UI so that you don’t have to start from scratch when you want to tweak a page.
幸运的是,默认的 Identity UI 设计为可增量替换,因此您可以覆盖单个页面,而无需自己重新构建整个 UI。最重要的是,Visual Studio 和 .NET CLI 都具有允许您在默认 UI 中搭建任何(或所有)页面的基架的功能,这样当您想要调整页面时,就不必从头开始。
DEFINITION Scaffolding is the process of generating files in your project that serve as the basis for customization. The Identity scaffolder adds Razor Pages in the correct locations so they override equivalent pages with the default UI. Initially, the code in the scaffolded pages matches that in the default Identity UI, but you are free to customize it.
定义:基架是在项目中生成文件作为自定义基础的过程。Identity 基架将 Razor Pages 添加到正确的位置,以便它们使用默认 UI 覆盖等效页面。最初,基架页面中的代码与默认 Identity UI 中的代码匹配,但您可以自由自定义它。
As an example of the changes you can easily make, we’ll scaffold the registration page and remove the additional information section about external providers. The following steps describe how to scaffold the Register.cshtml page in Visual Studio:
作为您可以轻松进行的更改的示例,我们将搭建注册页面并删除有关外部提供商的其他信息部分。以下步骤介绍如何在 Visual Studio 中搭建 Register.cshtml 页面的基架:
- Add the Microsoft.VisualStudio.Web.CodeGeneration.Design and Microsoft .EntityFrameworkCore.Tools NuGet packages to your project file, if they’re not already added. Visual Studio uses these packages to scaffold your application correctly, and without them you may get an error running the scaffolder:
添加 Microsoft.VisualStudio.Web.CodeGeneration.Design 和 Microsoft 。EntityFrameworkCore.Tools NuGet 包添加到项目文件中(如果尚未添加)。Visual Studio 使用这些包来正确搭建应用程序基架,如果没有它们,运行 Scaffolder 时可能会遇到错误:
<PackageReference Version="7.0.0"
Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" />
<PackageReference Version="7.0.0"
Include="Microsoft.EntityFrameworkCore.Tools" />
-
Ensure that your project builds. If it doesn’t build, the scaffolder will fail before adding your new pages.
确保您的项目构建成功。如果它没有构建,则 scaffolder 将在添加新页面之前失败。 -
Right-click your project, and choose Add > New Scaffolded Item from the contextual menu.
右键单击您的项目,然后从上下文菜单中选择 Add > New Scaffolded Item (添加新基架项)。 -
In the selection dialog box, choose Identity from the category, and choose Add.
在选择对话框中,从类别中选择 Identity (身份),然后选择 Add (添加)。 -
In the Add Identity dialog box, select the Account/Register page, and select your application’s AppDbContext as the Data context class, as shown in figure 23.12. Choose Add to scaffold the page.
在 Add Identity 对话框中,选择 Account/Register 页面,然后选择应用程序的 AppDbContext 作为 Data 上下文类,如图 23.12 所示。选择 Add (添加) 以搭建页面基架。
Figure 23.12 Using Visual Studio to scaffold Identity pages. The generated Razor Pages will override the versions provided by the default UI.
图 23.12 使用 Visual Studio 搭建 Identity 页面的基架。生成的 Razor Pages 将替代默认 UI 提供的版本。
Tip To scaffold the registration page using the .NET CLI, install the required tools and packages as described in Microsoft’s “Scaffold Identity in ASP.NET Core projects” documentation: http://mng.bz/QPRv. Then run dotnet aspnet-codegenerator identity -dc RecipeApplication.Data.AppDbContext --files "Account.Register".
提示:要使用 .NET CLI 搭建注册页面的基架,请按照 Microsoft 的“ASP.NET Core 项目中的基架标识”文档中所述安装所需的工具和包:http://mng.bz/QPRv。然后运行 dotnet aspnet-codegenerator identity -dc RecipeApplication.Data.AppDbContext --files “Account.Register”。
Visual Studio builds your application and then generates the Register.cshtml page for you, placing it in the Areas/Identity/Pages/Account folder. It also generates several supporting files, as shown in figure 23.13. These are required mostly to ensure that your new Register.cshtml page can reference the remaining pages in the default Identity UI.
Visual Studio 生成应用程序,然后生成 Register.cshtml 页面,将其放置在 Areas/Identity/Pages/Account 文件夹中。它还会生成几个支持文件,如图 23.13 所示。这些主要是为了确保新的 Register.cshtml 页面可以引用默认标识 UI 中的其余页面。
Figure 23.13 The scaffolder generates the Register.cshtml Razor Page, along with supporting files required to integrate with the remainder of the default Identity UI.
图 23.13 基架生成 Register.cshtml Razor 页面,以及与默认标识 UI 的其余部分集成所需的支持文件。
We’re interested in the Register.cshtml page, as we want to customize the UI on the Register page, but if we look inside the code-behind page, Register.cshtml.cs, we see how much complexity the default Identity UI is hiding from us. It’s not insurmountable (we’ll customize the page handler in section 23.6), but it’s always good to avoid writing code if we can help it.
我们对 Register.cshtml 页面感兴趣,因为我们希望自定义 Register 页面上的 UI,但如果我们查看代码隐藏页面 Register.cshtml.cs,我们会看到默认身份 UI 对我们隐藏了多少复杂性。这并不是不可克服的(我们将在 Section 23.6 中自定义页面处理程序),但如果我们可以提供帮助,避免编写代码总是好的。
Now that you have the Razor template in your application, you can customize it to your heart’s content. The downside is that you’re now maintaining more code than you were with the default UI. You didn’t have to write it, but you may still have to update it when a new version of ASP.NET Core is released.
现在,您的应用程序中已有 Razor 模板,您可以根据自己的喜好对其进行自定义。缺点是,您现在维护的代码比使用默认 UI 时要多。您不必编写它,但在 ASP.NET Core 的新版本发布时,您可能仍需要更新它。
I like to use a bit of a trick when it comes to overriding the default Identity UI like this. In many cases, you don’t want to change the page handlers for the Razor Page—only the Razor view. You can achieve this by deleting the Register.cshtml.cs PageModel file, and pointing your newly scaffolded .cshtml file at the original PageModel, which is part of the default UI NuGet package.
在覆盖像这样的默认身份 UI 时,我喜欢使用一些技巧。在许多情况下,您不希望更改 Razor 页面的页面处理程序,而只需要更改 Razor 视图。为此,您可以删除Register.cshtml.cs PageModel 文件,并将新搭建的 .cshtml 文件指向原始 PageModel,该 PageModel 是默认 UI NuGet 包的一部分。
The other benefit of this approach is that you can delete some of the other files that were autoscaffolded. In total, you can make the following changes:
此方法的另一个好处是,您可以删除一些自动基架的其他文件。总的来说,您可以进行以下更改:
• Update the @model directive in Register.cshtml to point to the default UI PageModel:
更新 Register.cshtml 中的 @model 指令以指向默认 UI PageModel:
@model Microsoft.AspNetCore.Identity.UI.V5.Pages.Account.Internal.RegisterModel
• Update Areas/Identity/Pages/_ViewImports.cshtml to the following:
将 Areas/Identity/Pages/_ViewImports.cshtml 更新为以下内容:
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
• Delete Areas/Identity/Pages/_ValidationScriptsPartial.cshtml.
• Delete Areas/Identity/Pages/Account/Register.cshtml.cs.
• Delete Areas/Identity/Pages/Account/_ViewImports.cshtml.
After making all these changes, you’ll have the best of both worlds: you can update the default UI Razor Pages HTML without taking on the responsibility of maintaining the default UI code-behind.
进行所有这些更改后,你将获得两全其美的效果:可以更新默认 UI Razor Pages HTML,而无需负责维护默认 UI 代码隐藏。
Tip In the source code for the book, you can see these changes in action, where the Register view has been customized to remove the references to external identity providers.
提示:在本书的源代码中,您可以看到这些实际更改,其中 Register 视图已自定义以删除对外部身份提供者的引用。
Unfortunately, it’s not always possible to use the default UI PageModel. Sometimes you need to update the page handlers, such as when you want to change the functionality of your Identity area rather than only the look and feel. A common requirement is needing to store additional information about a user, as you’ll see in the next section.
遗憾的是,并非总是可以使用默认的 UI PageModel。有时,您需要更新页面处理程序,例如,当您想要更改 Identity 区域的功能,而不仅仅是外观时。一个常见的要求是需要存储有关用户的其他信息,您将在下一节中看到。
23.6 Managing users: Adding custom data to users
23.6 管理用户:向用户添加自定义数据
In this section you’ll see how to customize the ClaimsPrincipal assigned to your users by adding claims to the AspNetUserClaims table when the user is created. You’ll also see how to access these claims in your Razor Pages and templates.
在本节中,您将了解如何通过在创建用户时向 AspNetUserClaims 表添加声明来自定义分配给用户的 ClaimsPrincipal。您还将了解如何在 Razor 页面和模板中访问这些声明。
Often, the next step after adding Identity to an application is customizing it. The default templates require only an email and password to register. What if you need more details, like a friendly name for the user? Also, I’ve mentioned that we use claims for security, so what if you want to add a claim called IsAdmin to certain users?
通常,将 Identity 添加到应用程序后的下一步是对其进行自定义。默认模板只需要电子邮件和密码即可注册。如果您需要更多详细信息,例如用户的友好名称,该怎么办?此外,我还提到过我们使用声明来实现安全性,那么,如果您想向某些用户添加一个名为 IsAdmin 的声明,该怎么办?
You know that every user principal has a collection of claims, so conceptually, adding any claim requires adding it to the user’s collection. There are two main times that you would want to grant a claim to a user:
您知道每个用户主体都有一个声明集合,因此从概念上讲,添加任何声明都需要将其添加到用户的集合中。您希望向用户授予声明的主要时间有两个:
• For every user, when they register on the app—For example, you might want to add a Name field to the Register form and add that as a claim to the user when they register.
对于每个用户,当他们在应用程序上注册时 - 例如,您可能希望将“名称”字段添加到“注册”表单中,并在用户注册时将其作为声明添加到用户。
• Manually, after the user has registered—This is common for claims used as permissions, where an existing user might want to add an IsAdmin claim to a specific user after they have registered on the app.
在用户注册后手动 - 这在用作权限的声明中很常见,其中现有用户可能希望在特定用户注册应用程序后向特定用户添加 IsAdmin 声明。
In this section I’ll show you the first approach, automatically adding new claims to a user when they’re created. The latter approach is more flexible and ultimately is the approach many apps will need, especially line-of-business apps. Luckily, there’s nothing conceptually difficult to it; it requires a simple UI that lets you view users and add a claim through the same mechanism I’ll show here.
在本节中,我将向您展示第一种方法,即在创建用户时自动向用户添加新声明。后一种方法更灵活,最终是许多应用程序需要的方法,尤其是业务线应用程序。幸运的是,它在概念上没有什么困难;它需要一个简单的 UI,允许您通过我将在此处展示的相同机制查看用户并添加声明。
Tip Another common approach is to customize the IdentityUser entity, by adding a Name property, for example. This approach is sometimes easier to work with if you want to give users the ability to edit that property. Microsoft’s “Add, download, and delete custom user data to Identity in an ASP.NET Core project” documentation describes the steps required to achieve that: http://mng.bz/aoe7.
提示:另一种常见方法是自定义 IdentityUser 实体,例如,通过添加 Name 属性。如果您想让用户能够编辑该属性,这种方法有时更容易使用。Microsoft 的“在 ASP.NET Core 项目中向 Identity 添加、下载和删除自定义用户数据”文档介绍了实现此目的所需的步骤:http://mng.bz/aoe7。
Let’s say you want to add a new Claim to a user, called FullName. A typical approach would be as follows:
假设您要向名为 FullName 的用户添加新声明。典型的方法如下:
-
Scaffold the Register.cshtml Razor Page, as you did in section 23.5.
搭建 Register.cshtml Razor 页面的基架,就像在第 23.5 节中所做的那样。 -
Add a Name field to the InputModel in the Register.cshtml.cs PageModel.
将 Name 字段添加到 Register.cshtml.cs PageModel 中的 InputModel。 -
Add a Name input field to the Register.cshtml Razor view template.
将 Name 输入字段添加到 Register.cshtml Razor 视图模板。 -
Create the new ApplicationUser entity as before in the OnPost() page handler by calling CreateAsync on
UserManager<ApplicationUser>
.
像以前一样,通过在 UserManager 上调用 CreateAsync 在 OnPost() 页面处理程序中创建新的UserManager<ApplicationUser>
实体。 -
Add a new Claim to the user by calling UserManager.AddClaimAsync().
通过调用 UserManager.AddClaimAsync() 向用户添加新的声明。 -
Continue the method as before, sending a confirmation email or signing the user in if email confirmation is not required.
像以前一样继续该方法,发送确认电子邮件,如果不需要电子邮件确认,则让用户登录。
Steps 1–3 are fairly self-explanatory and require only updating the existing templates with the new field. Steps 4–6 take place in Register.cshtml.cs in the OnPostAsync() page handler, which is summarized in the following listing. In practice, the page handler has more error checking, boilerplate, extra features, and abstraction. I’ve simplified the code in listing 23.7 to focus on the additional lines that add the extra Claim to the ApplicationUser; you can find the full code in the sample code for this chapter.
步骤 1-3 相当不言自明,只需要使用新字段更新现有模板。步骤 4-6 在 OnPostAsync() 页面处理程序中Register.cshtml.cs进行,下面的清单对此进行了总结。在实践中,页面处理程序具有更多的错误检查、样板、额外功能和抽象。我简化了清单 23.7 中的代码,以专注于向 ApplicationUser 添加额外 Claim 的其他行;您可以在本章的示例代码中找到完整代码。
Listing 23.7 Adding a custom claim to a new user in the Register.cshtml.cs page
清单 23.7 在 Register.cshtml.cs 页面中为新用户添加自定义声明
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { #A
UserName = Input.Email, Email = Input.Email }; #A
var result = await _userManager.CreateAsync( #B
user, Input.Password); #B
if (result.Succeeded)
{
var claim = new Claim("FullName", Input.Name); #C
await _userManager.AddClaimAsync(user, claim); #D
var code = await _userManager #E
.GenerateEmailConfirmationTokenAsync(user); #E
await _emailSender.SendEmailAsync( #E
Input.Email, "Confirm your email", code ); #E
await _signInManager.SignInAsync(user); #F
return LocalRedirect(returnUrl);
}
foreach (var error in result.Errors) #G
{ #G
ModelState.AddModelError( #G
string.Empty, error.Description); #G
} #G
}
return Page(); #G
}
❶ Creates an instance of the ApplicationUser entity
创建 ApplicationUser 实体的实例
❷ Validates that the provided password meets requirements, and creates the user in the database
验证提供的密码是否满足要求,并在数据库中创建用户
❸ Creates a claim, with a string name of “FullName” and the provided value
创建字符串名称为“FullName”且提供的值的声明
❹ Adds the new claim to the ApplicationUser’s collection
将新声明添加到 ApplicationUser 的集合
❺ Sends a confirmation email to the user, if you have configured the email sender
向用户发送确认电子邮件(如果您已配置电子邮件发件人)
❻ Signs the user in by setting the HttpContext.User; the principal will include the custom claim
通过设置 HttpContext.User 来登录用户;主体将包含自定义声明
❼ There was a problem creating the user. Adds the errors to the ModelState and redisplays the page.
创建用户时出现问题。将错误添加到 ModelState 并重新显示页面。
Tip Listing 23.7 shows how you can add extra claims at registration time, but you will often need to add more data later, such as permission-related claims or other information. You will need to create additional endpoints and pages for adding this data, securing the pages as appropriate (so that users can’t update their own permissions, for example).
提示:清单 23.7 展示了如何在注册时添加额外的声明,但您通常需要稍后添加更多数据,例如与权限相关的声明或其他信息。您需要创建其他终端节点和页面来添加此数据,并根据需要保护页面(例如,使用户无法更新自己的权限)。
This is all that’s required to add the new claim, but you’re not using it anywhere currently. What if you want to display it? Well, you’ve added a claim to the ClaimsPrincipal, which was assigned to the HttpContext.User property when you called SignInAsync. That means you can retrieve the claims anywhere you have access to the ClaimsPrincipal—including in your page handlers and in view templates. For example, you could display the user’s FullName claim anywhere in a Razor template with the following statement:
这就是添加新声明所需的全部内容,但您目前没有在任何地方使用它。如果要显示它怎么办?嗯,您已经向 ClaimsPrincipal 添加了一个声明,该声明在调用 SignInAsync 时分配给 HttpContext.User 属性。这意味着您可以在任何有权访问 ClaimsPrincipal 的位置检索声明,包括在页面处理程序和视图模板中。例如,您可以使用以下语句在 Razor 模板中的任意位置显示用户的 FullName 声明:
@User.Claims.FirstOrDefault(x=>x.Type == "FullName")?.Value
This finds the first claim on the current user principal with a Type of "FullName" and prints the assigned value (or, if the claim is not found, prints nothing). The Identity system even includes a handy extension method that tidies up this LINQ expression (found in the System.Security.Claims namespace):
这将在当前用户主体上查找 Type 为 “FullName” 的第一个声明,并打印分配的值(或者,如果未找到声明,则不打印任何内容)。Identity 系统甚至包括一个方便的扩展方法,用于整理此 LINQ 表达式(位于 System.Security.Claims 命名空间中):
@User.FindFirstValue("FullName")
With that last tidbit, we’ve reached the end of this chapter on ASP.NET Core Identity. I hope you’ve come to appreciate the amount of effort using Identity can save you, especially when you make use of the default Identity UI package.
说到最后的花絮,我们关于 ASP.NET Core Identity 的本章已经结束了。我希望您已经意识到使用 Identity 可以节省大量精力,尤其是在使用默认的 Identity UI 包时。
Adding user accounts and authentication to an app is typically the first step in customizing your app further. Once you have authentication, you can have authorization, which lets you lock down certain actions in your app, based on the current user. In the next chapter you’ll learn about the ASP.NET Core authorization system and how you can use it to customize your apps; in particular, the recipe application, which is coming along nicely!
向应用程序添加用户帐户和身份验证通常是进一步自定义应用程序的第一步。获得身份验证后,您可以获得授权,从而允许您根据当前用户锁定应用程序中的某些作。在下一章中,您将了解 ASP.NET Core 授权系统以及如何使用它来自定义您的应用程序;特别是 recipe 应用程序,它进展顺利!
23.7 Summary
23.7 总结
Authentication is the process of determining who you are, and authorization is the process of determining what you’re allowed to do. You need to authenticate users before you can apply authorization.
身份验证是确定您是谁的过程,授权是确定允许您执行哪些作的过程。您需要先对用户进行身份验证,然后才能应用授权。
Every request in ASP.NET Core is associated with a user, also known as a principal. By default, without authentication, this is an anonymous user. You can use the claims principal to behave differently depending on who made a request.
ASP.NET Core 中的每个请求都与一个用户(也称为委托人)相关联。默认情况下,如果不进行身份验证,则此用户为匿名用户。您可以使用 claims principal 根据发出请求的人员来执行不同的行为。
The current principal for a request is exposed on HttpContext.User. You can access this value from your Razor Pages and views to find out properties of the user such as their, ID, name, or email.
请求的当前主体在 HttpContext.User 上公开。可以从 Razor 页面和视图访问此值,以查找用户的属性,例如他们的 ID、名称或电子邮件。
Every user has a collection of claims. These claims are single pieces of information about the user. Claims could be properties of the physical user, such as Name and Email, or they could be related to things the user has, such as HasAdminAccess or IsVipCustomer.
每个用户都有一个声明集合。这些声明是有关用户的单个信息。声明可以是物理用户的属性,例如 Name 和 Email,也可以与用户拥有的内容相关,例如 HasAdminAccess 或 IsVipCustomer。
Legacy versions of ASP.NET used roles instead of claims. You can still use roles if you need to, but you should typically use claims where possible.
旧版本的 ASP.NET 使用角色而不是声明。如果需要,您仍然可以使用角色,但通常应尽可能使用声明。
Authentication in ASP.NET Core is provided by AuthenticationMiddleware and a number of authentication services. These services are responsible for setting the current principal when a user logs in, saving it to a cookie, and loading the principal from the cookie on subsequent requests.
ASP.NET Core 中的身份验证由 AuthenticationMiddleware 和许多身份验证服务提供。这些服务负责在用户登录时设置当前主体,将其保存到 Cookie,并在后续请求中从 Cookie 加载主体。
The AuthenticationMiddleware is added automatically by WebApplication. You can ensure that it’s inserted at a specific point in the middleware pipeline by calling UseAuthentication(). It must be placed before any middleware that requires authentication, such as UseAuthorization().
AuthenticationMiddleware 由 WebApplication 自动添加。您可以通过调用 UseAuthentication() 来确保它插入到中间件管道中的特定点。它必须放在任何需要身份验证的中间件之前,例如 UseAuthorization()。
ASP.NET Core Identity handles low-level services needed for storing users in a database, ensuring that their passwords are stored safely, and for logging users in and out. You must provide the UI for the functionality yourself and wire it up to the Identity subsystem.
ASP.NET Core Identity 处理将用户存储在数据库中、确保其密码安全存储以及登录和注销用户所需的低级服务。您必须自己提供该功能的 UI,并将其连接到 Identity 子系统。
The Microsoft.AspNetCore.Identity.UI package provides a default UI for the Identity system and includes email confirmation, MFA, and external login provider support. You need to do some additional configuration to enable these features.
Microsoft.AspNetCore.Identity.UI 包为标识系统提供默认 UI,并包括电子邮件确认、MFA 和外部登录提供程序支持。您需要进行一些额外的配置才能启用这些功能。
The default template for Web Application with Individual Account Authentication uses ASP.NET Core Identity to store users in the database with EF Core. It includes all the boilerplate code required to wire the UI up to the Identity system.
具有个人帐户身份验证的 Web 应用程序的默认模板使用 ASP.NET Core Identity 将用户存储在具有 EF Core 的数据库中。它包括将 UI 连接到标识系统所需的所有样板代码。
You can use the UserManager<T>
class to create new user accounts, load them from the database, and change their passwords. SignInManager
您可以使用UserManager<T>
该类创建新的用户帐户,从数据库中加载它们,并更改其密码。SignInManager 用于通过为请求分配主体和设置身份验证 cookie 来登录和注销用户。默认 UI 会为您使用这些类,以方便用户注册和登录。
You can update an EF Core DbContext to support Identity by deriving from IdentityDbContext
您可以通过从 IdentityDbContext 派生来更新 EF Core DbContext 以支持 Identity,其中 TUser 是从 IdentityUser 派生的类。
You can add additional claims to a user using the UserManager
您可以使用 UserManager 向用户添加其他声明。AddClaimAsync(TUser user, Claim claim) 方法。当用户登录到您的应用时,这些声明将添加到 HttpContext.User 对象中。
Claims consist of a type and a value. Both values are strings. You can use standard values for types exposed on the ClaimTypes class, such as ClaimTypes.GivenName and ClaimTypes.FirstName, or you can use a custom string, such as "FullName".
声明由类型和值组成。这两个值都是字符串。您可以对 ClaimTypes 类上公开的类型使用标准值,例如 ClaimTypes.GivenName 和 ClaimTypes.FirstName,也可以使用自定义字符串,例如“FullName”。