ASP.NET MVC 应用程序中自定义表单身份验证的初学者教程






4.93/5 (83投票s)
本文将讨论如何在 ASP.NET MVC 应用程序中实现自定义表单身份验证。
引言
本文将从 MVC 的角度讨论 ASP.NET 角色和成员资格 API。我们将尝试了解如何使用默认的角色和成员资格提供程序在 MVC 应用程序中进行身份验证和授权。我们还将了解如何在 ASP.NET MVC 应用程序中实现自定义表单身份验证。
背景
当我们开发身份验证和授权是关键要求的应用程序时,我们会发现 ASP.NET 角色和成员资格功能非常有用。ASP.NET MVC 中表单身份验证的基本原理和机制与 ASP.NET Webforms
相同。但是,由于我们没有服务器控件,因此使用方式将与 Webforms 有所不同。尽管如此,由于表单身份验证的核心原理是相同的,我建议以下文章将快速回顾 ASP.NET 表单身份验证以及如何在 WebForms
应用程序中实现和自定义它。
身份验证和授权
身份验证意味着验证用户。在此步骤中,我们验证用户凭据以检查尝试登录的人是否正确。另一方面,授权是跟踪当前用户允许查看的内容和应向其隐藏的内容。这更像是在记录要向用户显示什么和不显示什么。
每当用户登录时,他都必须使用其凭据进行身份验证。一旦通过身份验证,他将被授权查看网站的资源/页面。这两个概念大多是同时进行的。
身份验证类型
在继续之前,让我们首先了解 ASP.NET 应用程序中主要使用的两种身份验证类型。
- Windows 身份验证:在此模式下,用户通过其 Windows 用户名和密码进行身份验证。在互联网场景中,不建议使用此方法。在互联网场景中,我们应始终使用“基于表单的身份验证”。
- 基于表单的身份验证:在这种身份验证类型中,用户需要明确提供其凭据,一旦服务器验证了这些凭据,用户就可以登录。
我们将在本文的其余部分详细讨论表单身份验证。
使用代码
当我们想在应用程序中提供身份验证和授权时,默认的 ASP.NET 角色和成员资格类非常方便。使用默认的成员资格 API 将使用成员资格数据库,并为我们提供管理用户角色和成员资格所需的所有功能。
ASP.NET 还提供了一种实现自定义角色和成员资格的方法,以便对事物进行更精细的控制。我们可能仍然会遇到需要拥有自己的数据库来跟踪用户及其角色的情况。原因可能如下:
- 我们有一个现有数据库,并且正在尝试使用它实现一个应用程序。
- 角色和成员资格功能对于我们的应用程序来说是多余的。
- 角色和成员资格功能不足以满足我们的应用程序需求,我们需要自定义数据。
因此,让我们首先讨论默认的成员资格 API 以及 Visual Studio 为我们提供的开箱即用功能。然后,我们将通过实现自定义表单身份验证来完全控制授权和身份验证。
表单身份验证
要启用表单身份验证,我们需要在应用程序中执行以下步骤。
- 配置应用程序以使用表单身份验证。
- 创建一个登录页面。
- 每当用户尝试访问受限区域时,将其推送到登录页面。
- 当用户尝试登录时,使用数据库验证其凭据。
- 如果登录成功,将用户名及其角色保存在 Session 变量中以供后续使用。
- 为用户创建身份验证票据(加密的 cookie)。我们可以使其持久或非持久。
- 通过身份验证票据促进用户/角色提取。
- 使用上一步中找到的用户/角色,并使用它创建一个主体,以便 ASP.NET 表单身份验证机制可以使用此数据。
现在我们将了解默认的成员资格 API 如何为我们完成所有这些事情,以及我们如何自行实现所有这些步骤。
默认成员资格 API
当我们创建一个 MVC 互联网应用程序时。Visual Studio 项目向导会为我们完成所有上述步骤。它将在我们应用程序的 App_data 目录中创建一个成员资格数据库(实际上,位置取决于 web.config
文件中指定的 connectionstring
)。
它将生成 Controller
代码,该代码将检查数据库以进行用户身份验证,创建身份验证 cookie,并根据我们数据库中配置的角色创建 Principal
。它还将生成身份验证所需的所有视图。下图显示了生成的 Controller
、视图和数据库。

角色和用户可以从代码或从 Web 网站管理工具 (WSAT)
中配置,方式与 WebForms
应用程序相同。因此,让我们使用 WSAT
创建一个名为“admin”的角色和两个用户“admin”和“user”。“admin”将属于“admin”角色,“user”不属于任何角色。

现在从应用程序的角度来看,我们只需要标记需要身份验证和授权的视图,默认的成员资格类和生成的类将负责执行身份验证和授权。
假设我们只希望通过身份验证的用户能够查看 Home/Index
页面。并且只有属于“Admin”角色的用户才能访问 Home/About
页面。为此,我们需要在控制器中使用授权属性修饰相应的操作,如下所示:
public class HomeController : Controller
{
[Authorize]
public ActionResult Index()
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
return View();
}
[Authorize(Roles="Admin")]
public ActionResult About()
{
return View();
}
}
如果我们使用默认的成员资格 API 和 Visual Studio 生成的身份验证和授权代码,那么只需要这些就可以进行身份验证和授权。如果我们需要一些附加功能,我们可以在 AccountController
类中进行一定程度的自定义。
注意:本文不包含默认成员资格用法的示例,因为它只是创建一个新的 MVC 3 互联网应用程序,所有代码都将由 Visual Studio 自身生成。强烈建议查看 AccountController
类以了解它是如何执行各种操作的。
自定义表单身份验证
现在,如果我们不想使用默认的成员资格 API 和 Visual Studio 生成的代码,那么我们可以选择实现我们自己的身份验证和授权机制。为此,我们将需要负责实现本文前面讨论过的表单身份验证所需的所有步骤。因此,让我们创建一个空的 MVC 3 应用程序,看看如何实现自定义表单身份验证。
配置表单身份验证
现在我们需要做的第一件事是配置应用程序以使用表单身份验证。这可以在 web.config 文件中完成。
<authentication mode="Forms">
<forms loginUrl="~/Account/Login" timeout="2880" />
</authentication>
准备用户数据库
现在让我们创建一个小的数据库,我们将用它来执行身份验证。这个数据库只包含两个用户,就像我们早期的旧示例一样:“admin”和“user”。“admin”在“admin”角色中,“user”不在任何角色中。

注意:此数据库既未优化也未规范化,因为这不是本文的主要目的。真实的数据库示例会更优化,或许也更复杂。密码肯定不会是明文的,它们要么被加密,要么被“哈希加盐”。
现在,为了执行数据访问,让我们使用实体框架,这样我们就不必编写创建模型和数据访问逻辑所需的所有样板代码。为我们的数据库生成的实体将如下所示:

创建控制器和视图
现在让我们继续创建一个将负责身份验证逻辑的控制器。我们将创建 Login
和 Logout
的功能,但其他功能(如用户创建和密码更改)可以很容易地以相同的方式实现(这是验证用户模型并在加密或哈希加盐后对表执行 CRUD 操作的问题)。
这是我们的控制器,包含 login
和 logout
操作
public ActionResult Login()
{
return View();
}
[HttpPost]
public ActionResult Login(User model, string returnUrl)
{
// Lets first check if the Model is valid or not
if (ModelState.IsValid)
{
using (userDbEntities entities = new userDbEntities())
{
string username = model.username;
string password = model.password;
// Now if our password was enctypted or hashed we would have done the
// same operation on the user entered password here, But for now
// since the password is in plain text lets just authenticate directly
bool userValid = entities.Users.Any(user => user.username == username && user.password == password);
// User found in the database
if (userValid)
{
FormsAuthentication.SetAuthCookie(username, false);
if (Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1 && returnUrl.StartsWith("/")
&& !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\\"))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
public ActionResult LogOff()
{
FormsAuthentication.SignOut();
return RedirectToAction("Index", "Home");
}
在上面的代码中,当用户尝试登录时,我们将检查具有给定用户凭据的用户是否存在于用户数据库中。如果存在,我们设置身份验证票据并继续。现在,如果我们的密码已加密或哈希化,我们将在检查数据库之前对用户输入的密码执行相同的操作,但由于密码是纯文本,因此我们直接进行身份验证。
现在,在继续之前,让我们看看将获取用户凭据并执行身份验证的视图。

现在让我们创建一个简单的 Controller
,它将包含两个操作。一个操作可以由任何经过身份验证的用户执行,另一个操作只能由“admin”角色中的用户执行。
public class HomeController : Controller
{
[Authorize]
public ActionResult Index()
{
ViewBag.Message = "This can be viewed only by authenticated users only";
return View();
}
[Authorize(Roles="admin")]
public ActionResult AdminIndex()
{
ViewBag.Message = "This can be viewed only by users in Admin role only";
return View();
}
}
现在,当我们运行应用程序时,我们可以看到当用户尝试访问 Home 控制器的视图时,他们将被要求输入凭据。成功登录后,他们将能够看到 Index 页面。

但是,如果我们尝试查看 AdminIndex
页面,我们将无法看到它。原因是目前用户的角色未被使用。
利用身份验证票据提取角色
现在要使用数据库中指定的角色,需要了解一件事。当使用表单身份验证时,每当需要身份验证时,ASP.NET 框架都会检查当前的 IPrinciple
类型对象。此 IPrinciple
类型对象中包含的用户 ID 和角色将决定用户是否被允许访问。
到目前为止,我们还没有编写代码将用户角色详细信息推送到此主体对象中。为此,我们需要在 global.asax
中重写一个名为 FormsAuthentication_OnAuthenticate
的方法。每当 ASP.NET 框架尝试根据当前主体检查身份验证和授权时,都会调用此方法。
我们现在需要做的是重写此方法。检查身份验证票证(因为用户已经过验证并且票证已创建),然后将此用户/角色信息提供给 IPrinciple
类型对象。我们也可以实现自定义的 Principle 类型,但为了简单起见,我们将简单地创建一个 GenericPriciple
对象并将我们的用户特定详细信息设置到其中
protected void FormsAuthentication_OnAuthenticate(Object sender, FormsAuthenticationEventArgs e)
{
if (FormsAuthentication.CookiesSupported == true)
{
if (Request.Cookies[FormsAuthentication.FormsCookieName] != null)
{
try
{
//let us take out the username now
string username = FormsAuthentication.Decrypt(Request.Cookies[FormsAuthentication.FormsCookieName].Value).Name;
string roles = string.Empty;
using (userDbEntities entities = new userDbEntities())
{
User user = entities.Users.SingleOrDefault(u => u.username == username);
roles = user.Roles;
}
//let us extract the roles from our own custom cookie
//Let us set the Pricipal with our user specific details
e.User = new System.Security.Principal.GenericPrincipal(
new System.Security.Principal.GenericIdentity(username, "Forms"), roles.Split(';'));
}
catch (Exception)
{
//somehting went wrong
}
}
}
}
注意:在 MVC 4 及更高版本中,此事件将不起作用。要在 MVC 4 及更高版本中使自定义表单身份验证起作用,我们需要将此代码放在 Application_PostAuthenticateRequest
事件中。
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
if (FormsAuthentication.CookiesSupported == true)
{
if (Request.Cookies[FormsAuthentication.FormsCookieName] != null)
{
try
{
//let us take out the username now
string username = FormsAuthentication.Decrypt(Request.Cookies[FormsAuthentication.FormsCookieName].Value).Name;
string roles = string.Empty;
using (userDbEntities entities = new userDbEntities())
{
User user = entities.Users.SingleOrDefault(u => u.username == username);
roles = user.Roles;
}
//let us extract the roles from our own custom cookie
//Let us set the Pricipal with our user specific details
HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(
new System.Security.Principal.GenericIdentity(username, "Forms"), roles.Split(';'));
}
catch (Exception)
{
//somehting went wrong
}
}
}
}

现在,当我们运行应用程序时,甚至可以访问 AdminIndex
页面。现在,我们已经具备了实现自定义表单身份验证所需的所有步骤。我们已成功在 ASP.NET MVC 应用程序中实现了自定义表单身份验证。
注意:本文包含的代码片段仅用于演示文章的概念,不遵循任何最佳实践。本文中的代码不具有生产质量,因此仅应从中获取逻辑。
关注点
在本文中,我们试图探讨 ASP.NET 角色和成员资格 API。我们看到了 MVC 项目如何附带默认的成员资格 API 实现以及一些默认的控制器和视图,这些可以用于轻松地在 MVC 应用程序中集成表单身份验证。我们还看到了如何完全控制表单身份验证并实现自定义表单身份验证。本文是从初学者的角度编写的。希望这篇文章能提供信息。
历史
- 2013 年 4 月 16 日: 第一版。