註:本文是【ASP.NET Identity系列教程】的第二篇。本系列教程詳細、完整、深入地介紹了微軟的ASP.NET Identity技術,描述瞭如何運用ASP.NET Identity實現應用程式的用戶管理,以及實現應用程式的認證與授權等相關技術,譯者希望本系列教程能成為掌握ASP.NET Id
註:本文是【ASP.NET Identity系列教程】的第二篇。本系列教程詳細、完整、深入地介紹了微軟的ASP.NET Identity技術,描述瞭如何運用ASP.NET Identity實現應用程式的用戶管理,以及實現應用程式的認證與授權等相關技術,譯者希望本系列教程能成為掌握ASP.NET Identity技術的一份完整而有價值的資料。讀者若是能夠按照文章的描述,一邊閱讀、一邊實踐、一邊理解,定能有意想不到的巨大收穫!希望本系列博文能夠得到廣大園友的高度推薦。
14 Applying ASP.NET Identity
14 運用ASP.NET Identity
In this chapter, I show you how to apply ASP.NET Identity to authenticate and authorize the user accounts created in the previous chapter. I explain how the ASP.NET platform provides a foundation for authenticating requests and how ASP.NET Identity fits into that foundation to authenticate users and enforce authorization through roles. Table 14-1 summarizes this chapter.
本章將演示如何將ASP.NET Identity用於對上一章中創建的用戶賬號進行認證與授權。我將解釋ASP.NET平臺對請求進行認證的基礎,並解釋ASP.NET Identity如何融入這種基礎對用戶進行認證,以及通過角色增強授權功能。表14-1描述了本章概要。
Problem 問題 |
Solution 解決方案 |
Listing 清單號 |
---|---|---|
Prepare an application for user authentication. 準備用戶認證的應用程式 |
Apply the Authorize attribute to restrict access to action methods and define a controller to which users will be redirected to provide credentials. 運用Authorize註解屬性來限制對動作方法的訪問,並定義一個對用戶重定向的控制器,以便讓用戶提供憑據 |
1–4 |
Authenticate a user. 認證用戶 |
Check the name and password using the FindAsync method defined by the user manager class and create an implementation of the IIdentity interface using the CreateIdentityMethod. Set an authentication cookie for subsequent requests by calling the SignIn method defined by the authentication manager class. 使用由用戶管理器類定義的FindAsync方法檢查用戶名和口令,並使用CreateIdentityMethod創建一個IIdentity介面的實現。通過調用由認證管理器類定義的SignIn方法,設置後繼請求的認證Cookie。 |
5 |
Prepare an application for role-based authorization. 準備基於角色授權的應用程式 |
Create a role manager class and register it for instantiation in the OWIN startup class. 創建一個角色管理器類,將其註冊為OWIN啟動類中的實例化 |
6–8 |
Create and delete roles. 創建和刪除角色 |
Use the CreateAsync and DeleteAsync methods defined by the role manager class. 使用由角色管理器類定義的CreateAsync和DeleteAsync方法。 |
9–12 |
Manage role membership. 管理角色成員 |
Use the AddToRoleAsync and RemoveFromRoleAsync methods defined by the user manager class. 使用由用戶管理器類定義的AddToRoleAsync和RemoveFromRoleAsync方法 |
13–15 |
Use roles for authorization. 使用角色進行授權 |
Set the Roles property of the Authorize attribute. 設置Authorize註解屬性的Roles屬性 |
16–19 |
Seed the database with initial content. 將初始化內容植入資料庫 |
Use the database context initialization class. 使用資料庫上下文的初始化類 |
20, 21 |
14.1 Preparing the Example Project
14.1 準備示例項目
In this chapter, I am going to continue working on the Users project I created in Chapter 13. No changes to the application components are required.
在本章,我打算繼續沿用第13章所創建的Users項目,不需要修改該應用程式的組件。
14.2 Authenticating Users
14.2 認證用戶
The most fundamental activity for ASP.NET Identity is to authenticate users, and in this section, I explain and demonstrate how this is done. Table 14-2 puts authentication into context.
ASP.NET Identity最基本的活動就是認證用戶,在本小節中,我將解釋並演示其做法。表14-2描述了認證的情形。
Question 問題 |
Answer 回答 |
---|---|
What is it? 什麼是認證? |
Authentication validates credentials provided by users. Once the user is authenticated, requests that originate from the browser contain a cookie that represents the user identity. 認證是驗證用戶提供的憑據。一旦用戶已被認證,源自該瀏覽器的請求便會含有表示該用戶標識的Cookie。 |
Why should I care? 為何要關心它? |
Authentication is how you check the identity of your users and is the first step toward restricting access to sensitive parts of the application. 認證是你檢查用戶標識的辦法,也是限制對應用程式敏感部分進行訪問的第一步。 |
How is it used by the MVC framework? 如何在MVC框架中使用它? |
Authentication features are accessed through the Authorize attribute, which is applied to controllers and action methods in order to restrict access to authenticated users. 認證特性是通過Authorize註解屬性進行訪問的,將該註解屬性運用於控制器和動作方法,目的是將訪問限制到已認證用戶。 |
Tip I use names and passwords stored in the ASP.NET Identity database in this chapter. In Chapter 15, I demonstrate how ASP.NET Identity can be used to authenticate users with a service from Google (Identity also supports authentication for Microsoft, Facebook, and Twitter accounts).
提示:本章會使用存儲在ASP.NET Identity資料庫中的用戶名和口令。在第15章中將演示如何將ASP.NET Identity用於認證享有Google服務的用戶(Identity還支持對Microsoft、Facebook以及Twitter賬號的認證)。
14.2.1 Understanding the Authentication/Authorization Process
14.2.1 理解認證/授權過程
The ASP.NET Identity system integrates into the ASP.NET platform, which means you use the standard MVC framework techniques to control access to action methods, such as the Authorize attribute. In this section, I am going to apply basic restrictions to the Index action method in the Home controller and then implement the features that allow users to identify themselves so they can gain access to it. Listing 14-1 shows how I have applied the Authorize attribute to the Home controller.
ASP.NET Identity系統集成到了ASP.NET平臺,這意味著你可以使用標準的MVC框架技術來控制對動作方法的訪問,例如使用Authorize註解屬性。在本小節中,我打算在Home控制中的Index動作方法上運用基本的限制,然後實現讓用戶對自己進行標識,以使他們能夠訪問。清單14-1演示瞭如何將Authorize註解屬性運用於Home控制器。
Listing 14-1. Securing the Home Controller
清單14-1. 實施Home控制器的安全
using System.Web.Mvc; using System.Collections.Generic;
namespace Users.Controllers {
public class HomeController : Controller {
[Authorize] public ActionResult Index() { Dictionary<string, object> data = new Dictionary<string, object>(); data.Add("Placeholder", "Placeholder"); return View(data); } } }
Using the Authorize attribute in this way is the most general form of authorization and restricts access to the Index action methods to requests that are made by users who have been authenticated by the application.
這種方式使用Authorize註解屬性是授權的最一般形式,它限制了對Index動作方法的訪問,由用戶發送給該動作方法的請求必須是應用程式已認證的用戶。
If you start the application and request a URL that targets the Index action on the Home controller (/Home/Index, /Home, or just /), you will see the error shown by Figure 14-1.
如果啟動應用程式,並請求以Home控制器中Index動作為目標的URL(/Home/Index、/Home或/),將會看到如圖14-1所示的錯誤。
Figure 14-1. Requesting a protected URL
圖14-1. 請求一個受保護的URL
The ASP.NET platform provides some useful information about the user through the HttpContext object, which is used by the Authorize attribute to check the status of the current request and see whether the user has been authenticated. The HttpContext.User property returns an implementation of the IPrincipal interface, which is defined in the System.Security.Principal namespace. The IPrincipal interface defines the property and method shown in Table 14-3.
ASP.NET平臺通過HttpContext對象提供一些關於用戶的有用信息,該對象由Authorize註解屬性使用的,以檢查當前請求的狀態,考察用戶是否已被認證。HttpContext.User屬性返回的是IPrincipal介面的實現,該介面是在System.Security.Principal命名空間中定義的。IPrincipal介面定義瞭如表14-3所示的屬性和方法。
Name 名稱 |
Description 描述 |
---|---|
Identity | Returns an implementation of the IIdentity interface that describes the user associated with the request. 返回IIdentity介面的實現,它描述了與請求相關聯的用戶 |
IsInRole(role) | Returns true if the user is a member of the specified role. See the “Authorizing Users with Roles” section for details of managing authorizations with roles. 如果用戶是指定角色的成員,則返回true。參見“以角色授權用戶”小節,其中描述了以角色進行授權管理的細節 |
The implementation of IIdentity interface returned by the IPrincipal.Identity property provides some basic, but useful, information about the current user through the properties I have described in Table 14-4.
由IPrincipal.Identity屬性返回的IIdentity介面實現通過一些屬性提供了有關當前用戶的一些基本卻有用的信息,表14-4描述了這些屬性。
Name 名稱 |
Description 描述 |
---|---|
AuthenticationType | Returns a string that describes the mechanism used to authenticate the user 返回一個字元串,描述了用於認證用戶的機制 |
IsAuthenticated | Returns true if the user has been authenticated 如果用戶已被認證,返回true。 |
Name | Returns the name of the current user 返回當前用戶的用戶名 |
Tip In Chapter 15 I describe the implementation class that ASP.NET Identity uses for the IIdentity interface, which is called ClaimsIdentity.
提示:第15章會描述ASP.NET Identity用於IIdentity介面的實現類,其名稱為ClaimsIdentity。
ASP.NET Identity contains a module that handles the AuthenticateRequest life-cycle event, which I described in Chapter 3, and uses the cookies sent by the browser to establish whether the user has been authenticated. I’ll show you how these cookies are created shortly. If the user is authenticated, the ASP.NET framework module sets the value of the IIdentity.IsAuthenticated property to true and otherwise sets it to false. (I have yet to implement the feature that will allow users to authenticate, which means that the value of the IsAuthenticated property is always false in the example application.)
ASP.NET Identity含有一個處理AuthenticateRequest生命周期事件(第3章曾做過描述)的模塊,並使用瀏覽器發送過來的Cookie確認用戶是否已被認證。我很快會演示如何創建這些Cookie。如果用戶已被認證,此ASP.NET框架模塊便會將IIdentity.IsAuthenticated屬性的值設置為true,否則設置為false。(此刻尚未實現讓用戶進行認證的特性,這意味著在本示例應用程式中,IsAuthenticated屬性的值總是false。)
The Authorize module checks the value of the IsAuthenticated property and, finding that the user isn’t authenticated, sets the result status code to 401 and terminates the request. At this point, the ASP.NET Identity module intercepts the request and redirects the user to the /Account/Login URL. This is the URL that I defined in the IdentityConfig class, which I specified in Chapter 13 as the OWIN startup class, like this:
Authorize模塊檢查IsAuthenticated屬性的值,會發現該用戶是未認證的,於是將結果狀態碼設置為401(未授權),並終止該請求。但在這一點處(這裡是ASP.NET Identity在請求生命周期中的切入點——譯者註),ASP.NET Identity模塊會攔截該請求,並將用戶重定向到/Account/Login URL。我在IdentityConfig類中已定義了此URL,IdentityConfig是第13章所指定的OWIN啟動類,如下所示:
using Microsoft.AspNet.Identity; using Microsoft.Owin; using Microsoft.Owin.Security.Cookies; using Owin; using Users.Infrastructure;
namespace Users { public class IdentityConfig { public void Configuration(IAppBuilder app) {
app.CreatePerOwinContext<AppUserManager>(AppUserManager.Create); app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, LoginPath = new PathString("/Account/Login"), }); } } }
The browser requests the /Account/Login URL, but since it doesn’t correspond to any controller or action in the example project, the server returns a 404 – Not Found response, leading to the error message shown in Figure 14-1.
瀏覽器請求/Account/Login時,但因為示例項目中沒有相應的控制器或動作,於伺服器返回了“404 – 未找到”響應,從而導致瞭如圖14-1所示的錯誤消息。
14.2.2 Preparing to Implement Authentication
14.2.2 實現認證的準備
Even though the request ends in an error message, the request in the previous section illustrates how the ASP.NET Identity system fits into the standard ASP.NET request life cycle. The next step is to implement a controller that will receive requests for the /Account/Login URL and authenticate the user. I started by adding a new model class to the UserViewModels.cs file, as shown in Listing 14-2.
雖然請求終止於一條錯誤消息,但上一小節的請求已勾畫出ASP.NET Identity系統是如何切入標準的ASP.NET請求生命周期的。下一個步驟是實現一個控制器,用它來接收對/Account/Login URL的請求,並認證用戶。我首先在UserViewModels.cs文件中添加了一個模型類,如清單14-2所示。
Listing 14-2. Adding a New Model Class to the UserViewModels.cs File
清單14-2. 在UserViewModels.cs文件中添加一個新的模型類
using System.ComponentModel.DataAnnotations;
namespace Users.Models {
public class CreateModel { [Required] public string Name { get; set; } [Required] public string Email { get; set; } [Required] public string Password { get; set; } }
public class LoginModel { [Required] public string Name { get; set; } [Required] public string Password { get; set; } } }
The new model has Name and Password properties, both of which are decorated with the Required attribute so that I can use model validation to check that the user has provided values.
新模型具有Name和Password屬性,兩者都用Required註解屬性進行了註釋,以使我能夠使用模型驗證來檢查用戶是否提供了這些屬性的值。
Tip In a real project, I would use client-side validation to check that the user has provided name and password values before submitting the form to the server, but I am going to keep things focused on identity and the server-side functionality in this chapter. See Pro ASP.NET MVC 5 for details of client-side form validation.
提示:在一個實際的項目中,我會在用戶將表單遞交到伺服器之前,使用客戶端驗證來檢查用戶已經提供了用戶名和口令的值,但在本章中,我打算把註意力集中在標識和伺服器端的功能方面。客戶端表單驗證的詳情可參閱Pro ASP.NET MVC 5一書。
I added an Account controller to the project, as shown in Listing 14-3, with Login action methods to collect and process the user’s credentials. I have not implemented the authentication logic in the listing because I am going to define the view and then walk through the process of validating user credentials and signing users into the application.
我在項目中添加了一個Account控制器,如清單14-3所示,其中帶有Login動作方法,用以收集和處理用戶的憑據。該清單尚未實現認證邏輯,因為我打算先定義視圖,然後再實現驗證用戶憑據的過程,並讓用戶簽入應用程式。
Listing 14-3. The Contents of the AccountController.cs File
清單14-3. AccountController.cs文件的內容
using System.Threading.Tasks; using System.Web.Mvc; using Users.Models;
namespace Users.Controllers {
[Authorize] public class AccountController : Controller {
[AllowAnonymous] public ActionResult Login(string returnUrl) { if (ModelState.IsValid) { } ViewBag.returnUrl = returnUrl; return View(); }
[HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public async Task<ActionResult> Login(LoginModel details, string returnUrl) { return View(details); } } }
Even though it doesn’t authenticate users yet, the Account controller contains some useful infrastructure that I want to explain separately from the ASP.NET Identity code that I’ll add to the Login action method shortly.
儘管它此刻尚未認證用戶,但Account控制器已包含了一些有用的基礎結構,我想通過ASP.NET Identity代碼對這些結構分別加以解釋,很快就會在Login動作方法中添加這些代碼。
First, notice that both versions of the Login action method take an argument called returnUrl. When a user requests a restricted URL, they are redirected to the /Account/Login URL with a query string that specifies the URL that the user should be sent back to once they have been authenticated. You can see this if you start the application and request the /Home/Index URL. Your browser will be redirected, like this:
首先要註意Login動作方法有兩個版本,它們都有一個名稱為returnUrl的參數。當用戶請求一個受限的URL時,他們被重定向到/Account/Login URL上,並帶有查詢字元串,該字元串指定了一旦用戶得到認證後將用戶返回的URL,如下所示:
/Account/Login?ReturnUrl=%2FHome%2FIndex
The value of the ReturnUrl query string parameter allows me to redirect the user so that navigating between open and secured parts of the application is a smooth and seamless process.
ReturnUrl查詢字元串參數的值可讓我能夠對用戶進行重定向,使應用程式公開和保密部分之間的導航成為一個平滑無縫的過程。
Next, notice the attributes that I have applied to the Account controller. Controllers that manage user accounts contain functionality that should be available only to authenticated users, such as password reset, for example. To that end, I have applied the Authorize attribute to the controller class and then used the AllowAnonymous attribute on the individual action methods. This restricts action methods to authenticated users by default but allows unauthenticated users to log in to the application.
下一個要註意的是運用於Account控制器的註解屬性。管理用戶賬號的控制器含有隻能由已認證用戶才能使用的功能,例如口令重置。為此,我在控制器類上運用了Authorize註解屬性,然後又在個別動作方法上運用了AllowAnonymous註解屬性。這會將這些動作方法預設限制到已認證用戶,但又能允許未認證用戶登錄到應用程式。
Finally, I have applied the ValidateAntiForgeryToken attribute, which works in conjunction with the Html.AntiForgeryToken helper method in the view and guards against cross-site request forgery. Cross-site forgery exploits the trust that your user has for your application and it is especially important to use the helper and attribute for authentication requests.
最後要註意的是,我運用了ValidateAntiForgeryToken註解屬性,該屬性與視圖中的Html.AntiForgeryToken輔助器方法聯合工作,防止Cross-Site Request Forgery(CSRF,跨網站請求偽造)的攻擊。CSRF會利用應用程式對用戶的信任,因此使用這個輔助器和註解屬性對於認證請求是特別重要的。
Tip you can learn more about cross-site request forgery at http://en.wikipedia.org/wiki/Cross-site_request_forgery.
提示:更多關於CSRF的信息,請參閱http://en.wikipedia.org/wiki/Cross-site_request_forgery。
My last preparatory step is to create the view that will be rendered to gather credentials from the user. Listing 14-4 shows the contents of the Views/Account/Login.cshtml file, which I created by right-clicking the Index action method and selecting Add View from the pop-up menu.
最後一項準備步驟是創建一個視圖,用以收集來自於用戶的憑據。清單14-4顯示了Views/Account/Login.cshtml文件的內容,這是通過右擊Index動作方法,然後從彈出菜單選擇“Add View(添加視圖)”而創建的。
Listing 14-4. The Contents of the Login.cshtml File
清單14-4. Login.cshtml文件的內容
@model Users.Models.LoginModel @{ ViewBag.Title = "Login";} <h2>Log In</h2>
@Html.ValidationSummary()
@using (Html.BeginForm()) { @Html.AntiForgeryToken(); <input type="hidden" name="returnUrl" value="@ViewBag.returnUrl" /> <div class="form-group"> <label>Name</label> @Html.TextBoxFor(x => x.Name, new { @class = "form-control" }) </div> <div class="form-group"> <label>Password</label> @Html.PasswordFor(x => x.Password, new { @class = "form-control" }) </div> <button class="btn btn-primary" type="submit">Log In</button> }
The only notable aspects of this view are using the Html.AntiForgeryToken helper and creating a hidden input element to preserve the returnUrl argument. In all other respects, this is a standard Razor view, but it completes the preparations for authentication and demonstrates the way that unauthenticated requests are intercepted and redirected. To test the new controller, start the application and request the /Home/Index URL. You will be redirected to the /Account/Login URL, as shown in Figure 14-2.
該視圖唯一要註意的方面是使用了Html.AntiForgeryToken輔助器,並創建了一個隱藏的input元素,以保護returnUrl參數。在其他方面,這是一個標準的Razor視圖,但它實現了認證的準備工作,並能演示被攔截且被重定向的未認證請求的情況。為了測試這個新的控制器,啟動應用程式,並請求/Home/Index。你將被重定向到/Account/Login,如圖14-2所示。
Figure 14-2. Prompting the user for authentication credentials
圖14-2. 提示用戶輸入認證憑據
14.2.3 Adding User Authentication
14.2.3 添加用戶認證
Requests for protected URLs are being correctly redirected to the Account controller, but the credentials provided by the user are not yet used for authentication. In Listing 14-5, you can see how I have completed the implementation of the Login action.
發送給受保護URL的請求會被正確地重定向到Account控制器,但由用戶提供的憑據尚未被用於認證。從清單14-5可以看出如何完成Login動作的實現。
Listing 14-5. Adding Authentication to the AccountController.cs File
清單14-5. 在AccountController.cs文件中添加認證
using System.Threading.Tasks; using System.Web.Mvc; using Users.Models; using Microsoft.Owin.Security; using System.Security.Claims; using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.Owin; using Users.Infrastructure; using System.Web;
namespace Users.Controllers {
[Authorize] public class AccountController : Controller {
[AllowAnonymous] public ActionResult Login(string returnUrl) { ViewBag.returnUrl = returnUrl; return View(); }
[HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public async Task<ActionResult> Login(LoginModel details, string returnUrl) { if (ModelState.IsValid) { AppUser user = await UserManager.FindAsync(details.Name, details.Password); if (user == null) { ModelState.AddModelError("", "Invalid name or password."); } else { ClaimsIdentity ident = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie); AuthManager.SignOut(); AuthManager.SignIn(new AuthenticationProperties { IsPersistent = false}, ident); return Redirect(returnUrl); } } ViewBag.returnUrl = returnUrl; return View(details); }
private IAuthenticationManager AuthManager { get { return HttpContext.GetOwinContext().Authentication; } } private AppUserManager UserManager { get { return HttpContext.GetOwinContext().GetUserManager<AppUserManager>(); } } } }
The simplest part is checking the credentials, which I do through the FindAsync method of the AppUserManager class, which you will remember as the user manager class from Chapter 13:
最簡單的部分是檢查憑據,這是通過AppUserManager類的FindAsync方法來做的,你可能還記得,AppUserManager是第13章的用戶管理器類。
... AppUser user = await UserManager.FindAsync(details.Name, details.Password); ...
I will be using the AppUserManager class repeatedly in the Account controller, so I defined a property called UserManager that returns the instance of the class using the GetOwinContext extension method for the HttpContext class, just as I did for the Admin controller in Chapter 13.
我會在Account控制器中反覆使用AppUserManager類,因此定義了一個名稱為的UserManager屬性,它使用HttpContext類的GetOwinContext擴展方法來返回AppUserManager類的實例。
The FindAsync method takes the account name and password supplied by the user and returns an instance of the user class (AppUser in the example application) if the user account exists and if the password is correct. If there is no such account or the password doesn’t match the one stored in the database, then the FindAsync method returns null, in which case I add an error to the model state that tells the user that something went wrong.
FindAsync方法以用戶提供的賬號名和口令為參數,併在該用戶賬號存在且口令正確時,返回一個用戶類(此例中的AppUser)的實例。如果無此賬號,或者與資料庫存儲的不匹配,那麼FindAsync方法返回空(null),出現這種情況時,我給模型狀態添加了一條錯誤消息,告訴用戶可能出錯了。
If the FindAsync method does return an AppUser object, then I need to create the cookie that the browser will send in subsequent requests to show they are authenticated. Here are the relevant statements:
如果FindAsync方法確實返回了AppUser對象,那麼則需要創建Cookie,瀏覽器會在後繼的請求中發送這個Cookie,表明他們是已認證的。以下是有關語句:
... ClaimsIdentity ident = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie); AuthManager.SignOut(); AuthManager.SignIn(new AuthenticationProperties {IsPersistent = false}, ident); return Redirect(returnUrl); ...
The first step is to create a ClaimsIdentity object that identifies the user. The ClaimsIdentity class is the ASP.NET Identity implementation of the IIdentity interface that I described in Table 14-4 and that you can see used in the “Using Roles for Authorization” section later in this chapter.
第一步是創建一個標識該用戶的ClaimsIdentity對象。ClaimsIdentity類是表14-4所描述的IIdentity介面的ASP.NET Identity實現,可以在本章稍後的“使用角色授權”小節中看到它的使用。
Tip Don’t worry about why the class is called ClaimsIdentity at the moment. I explain what claims are and how they can be used in Chapter 15.
提示:此刻不必關心這個類為什麼要調用ClaimsIdentity,第15章會解釋什麼是聲明(Claims),並介紹如何使用它們。
Instances of ClaimsIdentity are created by calling the user manager CreateIdentityAsync method, passing in a user object and a value from the DefaultAuthenticationTypes enumeration. The ApplicationCookie value is used when working with individual user accounts.
ClaimsIdentity的實例是調用用戶管理器的CreateIdentityAsync方法而創建的,在其中傳遞了一個用戶對象和DefaultAuthenticationTypes枚舉中的一個值。在使用個別用戶賬號進行工作時,會用到ApplicationCookie值。
The next step is to invalidate any existing authentication cookies and create the new one. I defined the AuthManager property in the controller because I’ll need access to the object it provides repeatedly as I build the functionality in this chapter. The property returns an implementation of the IAuthenticationManager interface that is responsible for performing common authentication options. I have described the most useful methods provided by the IAuthenticationManager interface in Table 14-5.
下一個步驟是讓已認證的Cookie失效,並創建一個新的Cookie。我在該控制器中定義了AuthManager屬性,因為在建立本章功能過程中,需要反覆訪問它所提供的對象。該屬性返回的是IAuthenticationManager介面的實現,它負責執行常規的認證選項。表14-5中描述了IAuthenticationManager介面所提供的最有用的方法。
Name 名稱 |
Description 描述 |
---|---|
SignIn(options, identity) | Signs the user in, which generally means creating the cookie that identifies authenticated requests 簽入用戶,這通常意味著要創建用來標識已認證請求的Cookie |
SignOut() | Signs the user out, which generally means invalidating the cookie that identifies authenticated requests 簽出用戶,這通常意味著使標識已認證用戶的Cookie失效 |
The arguments to the SignIn method are an AuthenticationProperties object that configures the authentication process and the ClaimsIdentity object. I set the IsPersistent property defined by the AuthenticationProperties object to true to make the authentication cookie persistent at the browser, meaning that the user doesn’t have to authenticate again when starting a new session. (There are other properties defined by the AuthenticationProperties class, but the IsPersistent property is the only one that is widely used at the moment.)
SignIn方法的參數是一個AuthenticationProperties對象,用以配置認證過程以及ClaimsIdentity對象。我將AuthenticationProperties對象定義的IsPersistent屬性設置為true,以使認證Cookie在瀏覽器中是持久化的,意即用戶在開始新會話時,不必再次進行認證。(AuthenticationProperties類還定義了一些其他屬性,但IsPersistent屬性是此刻唯一要廣泛使用的一個屬性。)
The final step is to redirect the user to the URL they requested before the authentication process started, which I do by calling the Redirect method.
最後一步是將用戶重定向到他們在認證過程開始之前所請求的URL,這是通過調用Redirect方法實現的。
CONSIDERING TWO-FACTOR AUTHENTICATION
考慮雙因數認證
I have performed single-factor authentication in this chapter, which is where the user is able to authenticate using a single piece of information known to them in advance: the password.
在本章中,我實行的是單因數認證,在這種場合中,用戶只需使用一個他們預知的單一信息片段:口令,便能夠進行認證。
ASP.NET Identity also supports two-factor authentication, where the user needs something extra, usually something that is given to the user at the moment they want to authenticate. The most common examples are a value from a SecureID token or an authentication code that is sent as an e-mail or text message (strictly speaking, the two factors can be anything, including fingerprints, iris scans, and voice recognition, although these are options that are rarely required for most web applications).
ASP.NET Identity還支持雙因數認證,在這種情況下,用戶需要一些附加信息,通常是在他們需要認證時才發給他們的某種信息。最常用的例子是SecureID令牌的值,或者是通過E-mail發送的認證碼或文本消息(嚴格地講,第二因數可以是任何東西,包括指紋、眼瞳掃描、聲音識別等,儘管這些是在大多數Web應用程式中很少需要用到的選項。)
Security is increased because an attacker needs to know the user’s password and have access to whatever provides the second factor, such an e-mail account or cell phone.
這樣增加了安全性,因為攻擊者需要知道用戶的口令,並且能夠對提供第二因數的客戶端進行訪問,如E-mail賬號或行動電話等。
I don’t show two-factor authentication in the book for two reasons. The first is that it requires a lot of preparatory work, such as setting up the infrastructure that distributes the second-factor e-mails and texts and implementing the validation logic, all of which is beyond the scope of this book.
本章不演示雙因數認證有兩個原因。第一是它需要許多準備工作,例如要建立分發第二因數的郵件和文本的基礎架構,並實現驗證邏輯,這些都超出了本書的範圍。
The second reason is that two-factor authentication forces the user to remember to jump through an additional hoop to authenticate, such as remembering their phone or keeping a security token nearby, something that isn’t always appropriate for web applications. I carried a SecureID token of one sort or another for more than a decade in various jobs, and I lost count of the number of times that I couldn’t log in to an employer’s system because I left the token at home.
第二個原因是雙因數認證強制用戶要記住一個額外的認證令牌,例如,要記住他們的電話,或者將安全令牌帶在身邊,這對Web應用程式而言,並非總是合適的。我十幾年在各種工作中都帶著這種或那種SecureID令牌,而且我有數不清的次數無法登錄雇員系統,因為我將令牌丟在了家裡。
If you are interested in two-factor security, then I recommend relying on a third-party provider such as Google for authentication, which allows the user to choose whether they want the additional security (and inconvenience) that two-factor authentication provides. I demonstrate third-party authentication in Chapter 15.
如果對雙因數安全性有興趣,那麼我建議你依靠第三方提供器,例如Google認證,它允許用戶選擇是否希望使用雙因數提供的附加安全性(而且是不方便的)。第15章將演示第三方認證。
14.2.4 Testing Authentication
14.2.4 測試認證
To test user authentication, start the application and request the /Home/Index URL. When redirected to the /Account/Login URL, enter the details of one of the users I listed at the start of the chapter (for instance, the name joe and the password MySecret). Click the Log In button, and your browser will be redirected back to the /Home/Index URL, but this time it will submit the authentication cookie that grants it access to the action method, as shown in Figure 14-3.
為了測試用戶認證,啟動應用程式,並請求/Home/Index URL。當被重定向到/Account/Login URL時,輸入本章開始時列出的一個用戶的細節(例如,姓名為joe,口令為MySecret)。點擊“Log In(登錄)”按鈕,你的瀏覽器將被重定向,回到/Home/Index URL,但這次它將遞交認證Cookie,被准予訪問該動作方法,如圖14-3所示。
Figure 14-3. Authenticating a user
圖14-3. 認證用戶
Tip You can use the browser F12 tools to see the cookies that are used to identify authenticated requests.
提示:可以用瀏覽器的F12工具,看到用來標識已認證請求的Cookie。
14.3 Authorizing Users with Roles
14.3 以角色授權用戶
In the previous section, I applied the Authorize attribute in its most basic form, which allows any authenticated user to execute the action method. In this section, I will show you how to refine authorization to give finer-grained control over which users can perform which actions. Table 14-6 puts authorization in context.
上一小節以最基本的形式運用了Authorize註解屬性,這允許任何已認證用戶執行動作方法。在本小節中,將展示如何精煉授權,以便在用戶能夠執行的動作上有更細粒度的控制。表14-6描述了授權的情形。
Question 問題 |
Answer 答案 |
---|---|
What is it? 這是什麼 |
Authorization is the process of granting access to controllers and action methods to certain users, generally based on role membership. 授權是將控制器和動作的准許訪問限制到特定用戶,通常是基於角色的成員 |
Why should I care? 為何要關註它 |
Without roles, you can differentiate only between users who are authenticated and those who are not. Most applications will have different types of users, such as customers and administrators. 沒有角色,你只能在已認證用戶和未認證用戶之間加以區分。大多數應用程式均有不同類型的用戶,例如客戶和管理員等 |
How is it used by the MVC framework? 在MVC框架中如何使用 |
Roles are used to enforce authorization through the Authorize attribute, which is applied to controllers and action methods. 角色通過Authorize註解屬性可用於強制授權,Authorize可用於控制器和動作方法 |
Tip In Chapter 15, I show you a different approach to authorization using claims, which are an advanced ASP.NET Identity feature.
提示:第15章將使用Claims(聲明)來演示不同的授權辦法,Claims是一種高級的ASP.NET Identity特性。
14.3.1 Adding Support for Roles
14.3.1 添加角色支持
ASP.NET Identity provides a strongly typed base class for accessing and managing roles called RoleManager<T> , where T is the implementation of the IRole interface supported by the storage mechanism used to represent roles. The Entity Framework uses a class called IdentityRole to implement the IRole interface, which defines the properties shown in Table 14-7.
ASP.NET Identity為訪問和管理角色提供了一個強類型的基類,叫做RoleManager<T> ,其中T是IRole介面的實現,該實現得到了用來表示角色的存儲機制的支持。Entity Framework實現了IRole接口,使用的是一個名稱為IdentityRole的類,它定義瞭如表14-7所示的屬性。
Name 名稱 |
Description 描述 |
---|---|
Id | Defines the unique identifier for the role 定義角色的唯一標識符 |
Name | Defines the name of the role 定義角色名稱 |
Users | Returns a collection of IdentityUserRole objects that represents the members of the role 返回一個代表角色成員的IdentityUserRole對象集合 |
I don’t want to leak references to the IdentityRole class throughout my application because it ties me to the Entity Framework for storing role data, so I start by creating an application-specific role class that is derived from IdentityRole. I added a class file called AppRole.cs to the Models folder and used it to define the class shown in Listing 14-6.
我不希望在整個應用程式中都暴露對IdentityRole類的引用,因為它為了存儲角色數據,將我綁定到了Entity Framework。為此,我首先創建了一個應用程式專用的角色類,它派生於IdentityRole。我在Models文件夾中添加了一個類文件,名稱為AppRole.cs,並用它定義了這個類,如清單14-6所示。
Listing 14-6. The Contents of the AppRole.cs File
清單14-6. AppRole文件的內容
using Microsoft.AspNet.Identity.EntityFramework;
namespace Users.Models { public class AppRole : IdentityRole {
public AppRole() : base() {}
public AppRole(string name) : base(name) { } } }
The RoleManager<T> class operates on instances of the IRole implementation class through the methods and properties shown in Table 14-8.
RoleManager<T> 類通過表14-8所示的方法和屬性對IRole實現類的實例進行操作。
Name 名稱 |
Description 描述 |
---|---|
CreateAsync(role) | Creates a new role 創建一個新角色 |
DeleteAsync(role) | Deletes the specified role 刪除指定角色 |
FindByIdAsync(id) | Finds a role by its ID 找到指定ID的角色 |
FindByNameAsync(name) | Finds a role by its name 找到指定名稱的角色 |
RoleExistsAsync(name) | Returns true if a role with the specified name exists 如果存在指定名稱的角色,返回true |
UpdateAsync(role) | Stores changes to the specified role 將修改存儲到指定角色 |
Roles | Returns an enumeration of the roles that have been defined 返回已被定義的角色枚舉 |
These methods follow the same basic pattern of theUserManager<T> class that I described in Chapter 13. Following the pattern I used for managing users, I added a class file called AppRoleManager.cs to the Infrastructure folder and used it to define the class shown in Listing 14-7.
這些方法與第13章描述的UserManager<T> 類有同樣的基本模式。按照對管理用戶所採用的模式,我在Infrastructure文件夾中添加了一個類文件,名稱為AppRoleManager.cs,用它定義瞭如清單14-7所示的類。
Listing 14-7. The Contents of the AppRoleManager.cs File
清單14-7. AppRoleManager.cs文件的內容
using System; using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.EntityFramework; using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin; using Users.Models;
namespace Users.Infrastructure {
public class AppRoleManager : RoleManager<AppRole>, IDisposable {
public AppRoleManager(RoleStore<AppRole> store) : base(store) { }
public static AppRoleManager Create( IdentityFactoryOptions<AppRoleManager> options, IOwinContext context) { return new AppRoleManager(new RoleStore<AppRole>(context.Get<AppIdentityDbContext>())); } } }
This class defines a Create method that will allow the OWIN start class to create instances for each request where Identity data is accessed, which means I don’t have to disseminate details of how role data is stored throughout the application. I can just obtain and operate on instances of the AppRoleManager class. You can see how I have registered the role manager class with the OWIN start class, IdentityConfig, in Listing 14-8. This ensures that instances of the AppRoleManager class are created using the same Entity Framework database context that is used for the AppUserManager class.
這個類定義了一個Create方法,它讓OWIN啟動類能夠為每一個訪問Identity數據的請求創建實例,這意味著在整個應用程式中,我不必散佈如何存儲角色數據的細節,卻能獲取AppRoleManager類的實例,並對其進行操作。在清單14-8中可以看到如何用OWIN啟動類(IdentityConfig)來註冊角色管理器類。這樣能夠確保,可以使用與AppUserManager類所用的同一個Entity Framework資料庫上下文,來創建AppRoleManager類的實例。
Listing 14-8. Creating Instances of the AppRoleManager Class in the IdentityConfig.cs File
清單14-8. 在IdentityConfig.cs文件中創建AppRoleManager類的實例
using Microsoft.AspNet.Identity; using Microsoft.Owin; using Microsoft.Owin.Security.Cookies; using Owin; using Users.Infrastructure;
namespace Users { public class IdentityConfig { public void Configuration(IAppBuilder app) {
app.CreatePerOwinContext<AppIdentityDbContext>(AppIdentityDbContext.Create); app.CreatePerOwinContext<AppUserManager>(AppUserManager.Create); app.CreatePerOwinContext<AppRoleManager>(AppRoleManager.Create); app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, LoginPath = new PathString("/Account/Login"), }); } } }
14.3.2 Creating and Deleting Roles
14.3.2 創建和刪除角色
Having prepared the application for working with roles, I am going to create an administration tool for managing them. I will start the basics and define action methods and views that allow roles to be created and deleted. I added a controller called RoleAdmin to the project, which you can see in Listing 14-9.
現在已經做好了應用程式使用角色的準備,我打算創建一個管理工具來管理角色。首先從基本的開始,定義能夠創建和刪除角色的動作方法和視圖。我在項目中添加了一個控制器,名稱為RoleAdmin,如清單14-9所示。
Listing 14-9. The Contents of the RoleAdminController.cs File
清單14-9. RoleAdminController.cs文件的內容
using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading.Tasks; using System.Web; using System.Web.Mvc; using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.Owin; using Users.Infrastructure; using Users.Models;
namespace Users.Controllers { public class RoleAdminController : Controller {
public ActionResult Index() { return View(RoleManager.Roles); }
public ActionResult Create() { return View(); }
[HttpPost] public async Task<ActionResult> Create([Required]string name) { if (ModelState.IsValid) { IdentityResult result = await RoleManager.CreateAsync(new AppRole(name)); if (result.Succeeded) { return RedirectToAction("Index"); } else { AddErrorsFromResult(result); } } return View(name); }
[HttpPost] public async Task<ActionResult> Delete(string id) { AppRole role = await RoleManager.FindByIdAsync(id); if (role != null) { IdentityResult result = await RoleManager.DeleteAsync(role); if (result.Succeeded) { return RedirectToAction("Index"); } else { return View("Error", result.Errors); } } else { return View("Error", new string[] { "Role Not Found" }); } }
private void AddErrorsFromResult(IdentityResult result) { foreach (string error in result.Errors) { ModelState.AddModelError("", error); } }
private AppUserManager UserManager { get { return HttpContext.GetOwinContext().GetUserManager<AppUserManager>(); } }
private AppRoleManager RoleManager { get { return HttpContext.GetOwinContext().GetUserManager<AppRoleManager>(); } } } }
I have applied many of the same techniques that I used in the Admin controller in Chapter 13, including a UserManager property that obtains an instance of the AppUserManager class and an AddErrorsFromResult method that processes the errors reported in an IdentityResult object and adds them to the model state.
這裡運用了許多第13章中Admin控制器所採用的同樣技術,包括一個UserManager屬性,用於獲取AppUserManager類的實例;和一個AddErrorsFromResult方法,用來處理IdentityResult對象所報告的消息,並將消息添加到模型狀態。
I have also defined a RoleManager property that obtains an instance of the AppRoleManager class, which I used in the action methods to obtain and manipulate the roles in the application. I am not going to describe the action methods in detail because they follow the same pattern I used in Chapter 13, using the AppRoleManager class in place of AppUserManager and calling the methods I described in Table 14-8.
我還定義了RoleManager屬性,用來獲取AppRoleManager類的實例,在動作方法中用該實例獲取並維護應用程式的角色。我不打算詳細描述這些動作方法,因為它們遵循著與第13章同樣的模式,在使用AppUserManager的地方使用了AppRoleManager類,調用的是表14-8中的方法。
14.3.3 Creating the Views
14.3.3 創建視圖
The views for the RoleAdmin controller are standard HTML and Razor markup, but I have included them in this chapter so that you can re-create the example. I want to display the names of the users who are members of each role. The Entity Framework IdentityRole class defines a Users property that returns a collection of IdentityUserRole user objects representing the members of the role. Each IdentityUserRole object has a UserId property that returns the unique ID of a user, and I want to get the username for each ID. I added a class file called IdentityHelpers.cs to the Infrastructure folder and used it to define th