通过


使用身份验证和授权保护应用程序

Microsoft提供

下载 PDF

这是免费的 “NerdDinner”应用程序教程 的第 9 步,介绍如何使用 ASP.NET MVC 1 构建小型但完整的 Web 应用程序。

步骤 9 演示如何添加身份验证和授权来保护 NerdDinner 应用程序,以便用户需要注册并登录网站以创建新的晚餐,并且只有托管晚餐的用户以后才能对其进行编辑。

如果使用 ASP.NET MVC 3,建议遵循 MVC 3MVC 音乐应用商店 入门教程。

NerdDinner 步骤 9:身份验证和授权

现在,我们的 NerdDinner 应用程序授予任何访问网站的任何人创建和编辑任何晚餐的详细信息的能力。 让我们对此进行更改,以便用户需要注册并登录网站以创建新的晚餐,并添加一个限制,以便只有托管晚餐的用户以后才能对其进行编辑。

为此,我们将使用身份验证和授权来保护应用程序。

了解身份验证和授权

身份验证 是标识和验证访问应用程序的客户端的标识的过程。 更简单地说,这就是在识别最终用户访问网站时的身份。 ASP.NET 支持多种方式对浏览器用户进行身份验证。 对于 Internet Web 应用程序,使用的最常见身份验证方法称为“窗体身份验证”。 表单身份验证允许开发人员在其应用程序中创作 HTML 登录表单,然后验证最终用户针对数据库或其他密码凭据存储提交的用户名/密码。 如果用户名/密码组合正确,开发人员可以要求 ASP.NET 发出加密的 HTTP Cookie,以识别将来的请求中的用户。 我们将对 NerdDinner 应用程序使用表单身份验证。

授权 是确定经过身份验证的用户是否有权访问特定 URL/资源或执行某些操作的过程。 例如,在 NerdDinner 应用程序中,我们要授权只有登录的用户才能访问 /Dinners/Create URL 并创建新的 Dinners。 我们还希望添加授权逻辑,以便只有托管晚餐的用户才能编辑它 , 并拒绝对所有其他用户的编辑访问权限。

表单身份验证和账户控制器 (AccountController)

ASP.NET MVC 的默认 Visual Studio 项目模板会在创建新的 ASP.NET MVC 应用程序时自动启用表单身份验证。 它还会自动将预生成的帐户登录页实现添加到项目中,这使得在网站中集成安全性变得非常简单。

默认的 Site.master 母版页在网站右上角显示一个“登录”链接,当用户访问该网站时未进行身份验证:

单击“登录”链接会将用户转到 /Account/LogOn URL:

Nerd Dinner Log On 页面的屏幕截图。

尚未注册的访问者可以通过单击“注册”链接来执行此操作 , 这会将它们带到 /Account/Register URL,并允许他们输入帐户详细信息:

Nerd Dinner Create a New Account 页面的屏幕截图。

单击“注册”按钮将在 ASP.NET 成员身份系统中创建新用户,并使用表单身份验证在站点上对用户进行身份验证。

当用户登录时,Site.master 将更改页面右上角以输出“欢迎[用户名]!”消息,并呈现“注销”链接,而不是“登录”链接。 单击“注销”链接会注销用户:

Nerd Dinner Host a Dinner 窗体页面的屏幕截图。“欢迎”和“注销”按钮在右上角突出显示。

上述登录、注销和注册功能是在创建项目时由 Visual Studio 添加到项目的 AccountController 类中实现的。 AccountController 的 UI 是使用 \Views\Account 目录中的视图模板实现的:

Nerd Dinner 导航树的屏幕截图。帐户控制器点 c s 突出显示。“帐户文件夹”和“菜单项”也突出显示。

AccountController 类使用 ASP.NET Forms 身份验证系统在验证用户身份后颁发加密的身份验证 Cookie,并使用 ASP.NET 成员身份 API 存储和验证用户名/密码。 ASP.NET 成员身份 API 可扩展,允许使用任何密码凭据存储。 ASP.NET 附带内置成员身份提供程序实现,用于在 SQL 数据库或 Active Directory 中存储用户名/密码。

我们可以配置 NerdDinner 应用程序应使用的成员资格提供程序,方法是在项目的根目录中打开“web.config”文件并查找 <其中的成员身份> 部分。 创建项目时添加的默认 web.config 注册 SQL 成员资格提供程序,并将其配置为使用名为“ApplicationServices”的连接字符串来指定数据库位置。

默认“ApplicationServices”连接字符串(在 web.config 文件的 <connectionStrings> 节内指定)被配置为使用 SQL Express。 它指向一个名为“ASPNETDB.MDF”的 SQL Express 数据库,该数据库位于应用程序的 "App_Data" 目录下。 如果首次在应用程序中使用成员身份 API 时不存在此数据库,ASP.NET 将自动创建数据库并在其中预配适当的成员身份数据库架构:

Nerd Dinner 导航树的屏幕截图。应用程序数据已展开,并选择了 A S P NET D B 点 M D F。

如果我们不使用 SQL Express,而是想使用完整的 SQL Server 实例(或连接到远程数据库),我们只需更新 web.config 文件中的“ApplicationServices”连接字符串,并确保将适当的成员身份架构添加到它指向的数据库中。 可以在 \Windows\Microsoft.NET\Framework\v2.0.50727\ 目录中运行“aspnet_regsql.exe”实用工具,将成员资格和其他 ASP.NET 应用程序服务的适当架构添加到数据库中。

使用 [授权] 筛选器授权 /Dinners/Create URL

我们无需编写任何代码即可为 NerdDinner 应用程序启用安全身份验证和帐户管理实现。 用户可以向应用程序注册新帐户,以及登录/注销站点。

现在,我们可以向应用程序添加授权逻辑,并使用访问者的身份验证状态和用户名来控制他们在站点中无法执行的操作。 首先,将授权逻辑添加到 DinnersController 类的“创建”操作方法。 具体而言,我们将要求访问 /Dinners/Create URL 的用户必须登录。 如果未登录,我们会将它们重定向到登录页,以便他们可以登录。

实现此逻辑非常简单。 我们只需将 [Authorize] 筛选器属性添加到我们的创建操作方法,如下所示:

//
// GET: /Dinners/Create

[Authorize]
public ActionResult Create() {
   ...
} 

//
// POST: /Dinners/Create

[AcceptVerbs(HttpVerbs.Post), Authorize]
public ActionResult Create(Dinner dinnerToCreate) {
   ...
}

ASP.NET MVC 支持创建可用于实现可声明性应用于操作方法的可重用逻辑的“操作筛选器”。 [授权] 筛选器是 ASP.NET MVC 提供的内置操作筛选器之一,它使开发人员能够以声明方式将授权规则应用于操作方法和控制器类。

在没有任何参数(如上述)的情况下应用时,[授权]筛选器强制用户进行操作方法请求时必须登录 – 如果未登录,它会自动将浏览器重定向到登录 URL。 执行此操作时,最初请求的 URL 作为查询字符串参数传递(例如:/Account/LogOn?ReturnUrl=%2fDinners%2fCreate)。 然后,AccountController 会在用户登录后将用户重定向回最初请求的 URL。

[授权] 筛选器(可选)支持指定“Users”或“Roles”属性的功能,该属性可用于要求用户既登录,又在允许的用户列表或允许的安全角色的成员列表中。 例如,下面的代码仅允许两个特定用户“scottgu”和“billg”访问 /Dinners/Create URL:

[Authorize(Users="scottgu,billg")]
public ActionResult Create() {
    ...
}

不过,在代码中嵌入特定用户名往往相当不可维护。 更好的方法是定义代码检查的更高级别的“角色”,然后使用数据库或 Active Directory 系统将用户映射到角色(使实际用户映射列表可从代码外部存储)。 ASP.NET 包括内置角色管理 API 以及一组内置的角色提供程序(包括 SQL 和 Active Directory 的角色提供程序),可帮助执行此用户/角色映射。 然后,我们可以更新代码,以仅允许特定“管理员”角色内的用户访问 /Dinners/Create URL:

[Authorize(Roles="admin")]
public ActionResult Create() {
   ...
}

创建晚餐时使用 User.Identity.Name 属性

可以使用控制器基类上公开的 User.Identity.Name 属性检索请求当前登录用户的用户名。

早些时候,当我们实现 create() 操作方法的 HTTP-POST 版本时,我们已将 Dinner 的“HostedBy”属性硬编码为静态字符串。 现在,我们可以更新此代码以改用 User.Identity.Name 属性,并为创建 Dinner 的主机自动添加 RSVP:

//
// POST: /Dinners/Create

[AcceptVerbs(HttpVerbs.Post), Authorize]
public ActionResult Create(Dinner dinner) {

    if (ModelState.IsValid) {
    
        try {
            dinner.HostedBy = User.Identity.Name;

            RSVP rsvp = new RSVP();
            rsvp.AttendeeName = User.Identity.Name;
            dinner.RSVPs.Add(rsvp);

            dinnerRepository.Add(dinner);
            dinnerRepository.Save();

            return RedirectToAction("Details", new { id=dinner.DinnerID });
        }
        catch {
            ModelState.AddModelErrors(dinner.GetRuleViolations());
        }
    }

    return View(new DinnerFormViewModel(dinner));
}

由于我们已向 Create() 方法添加了 [Authorize] 属性,因此 ASP.NET MVC 可确保仅在访问 /Dinners/Create URL 的用户登录站点时执行操作方法。 因此,User.Identity.Name 属性值将始终包含有效的用户名。

编辑晚餐时使用 User.Identity.Name 属性

现在,让我们添加一些授权逻辑来限制用户,以便他们只能编辑他们自己举办的晚宴的属性。

为此,我们将首先将“IsHostedBy(username)”辅助方法添加到我们的 Dinner 对象(在我们之前创建的 Dinner.cs 分部类中)。 此帮助程序方法返回 true 或 false,具体取决于提供的用户名是否与 Dinner HostedBy 属性匹配,并封装执行不区分大小写的字符串比较所需的逻辑:

public partial class Dinner {

    public bool IsHostedBy(string userName) {
        return HostedBy.Equals(userName, StringComparison.InvariantCultureIgnoreCase);
    }
}

然后将 [Authorize] 属性添加到 DinnersController 类中的 Edit() 操作方法。 这将确保用户必须登录才能请求 /Dinners/Edit/[id] URL。

然后,我们可以将代码添加到使用 Dinner.IsHostedBy(username) 帮助程序方法的 Edit 方法,以验证登录的用户是否与 Dinner 主机匹配。 如果用户不是主机,我们将显示“InvalidOwner”视图并终止请求。 要执行此操作的代码如下所示:

//
// GET: /Dinners/Edit/5

[Authorize]
public ActionResult Edit(int id) {

    Dinner dinner = dinnerRepository.GetDinner(id);

    if (!dinner.IsHostedBy(User.Identity.Name))
        return View("InvalidOwner");

    return View(new DinnerFormViewModel(dinner));
}

//
// POST: /Dinners/Edit/5

[AcceptVerbs(HttpVerbs.Post), Authorize]
public ActionResult Edit(int id, FormCollection collection) {

    Dinner dinner = dinnerRepository.GetDinner(id);

    if (!dinner.IsHostedBy(User.Identity.Name))
        return View("InvalidOwner");

    try {
        UpdateModel(dinner);

        dinnerRepository.Save();

        return RedirectToAction("Details", new {id = dinner.DinnerID});
    }
    catch {
        ModelState.AddModelErrors(dinnerToEdit.GetRuleViolations());

        return View(new DinnerFormViewModel(dinner));
    }
}

然后,我们可以右键单击\Views\Dinners 目录,然后选择“添加>视图”菜单命令以创建新的“InvalidOwner”视图。 我们将用以下错误消息填充它:

<asp:Content ID="Title" ContentPlaceHolderID="TitleContent" runat="server">
    You Don't Own This Dinner
</asp:Content>

<asp:Content ID="Main" ContentPlaceHolderID="MainContent" runat="server">

    <h2>Error Accessing Dinner</h2>

    <p>Sorry - but only the host of a Dinner can edit or delete it.</p>

</asp:Content>

现在,当用户尝试编辑他们不拥有的晚餐时,他们将收到一条错误消息:

Nerd Dinner 网页上错误消息的屏幕截图。

我们可以对控制器中的 Delete() 操作方法重复相同的步骤,以锁定删除 Dinner 的权限,确保只有一个 Dinner 的组织者可以删除它。

我们正在从详细信息 URL 链接到 DinnersController 类的 Edit 和 Delete 操作方法:

Nerd Dinner 页面的屏幕截图。“编辑”和“删除”按钮被圈出在底部。详细信息 URL 被圈出在顶部。

目前,我们将显示“编辑”和“删除”操作链接,无论详细信息 URL 的访问者是否是晚餐的主持人。 让我们对此进行更改,以便仅当来访的用户是晚餐的所有者时,才会显示链接。

DinnersController 中的 Details() 操作方法检索 Dinner 对象,然后将其作为模型对象传递给视图模板:

//
// GET: /Dinners/Details/5

public ActionResult Details(int id) {

    Dinner dinner = dinnerRepository.GetDinner(id);

    if (dinner == null)
        return View("NotFound");

    return View(dinner);
}

我们可以使用 Dinner.IsHostedBy() 帮助程序方法更新视图模板,以有条件地显示/隐藏“编辑和删除”链接,如下所示:

<% if (Model.IsHostedBy(Context.User.Identity.Name)) { %>

   <%= Html.ActionLink("Edit Dinner", "Edit", new { id=Model.DinnerID }) %> |
   <%= Html.ActionLink("Delete Dinner", "Delete", new {id=Model.DinnerID}) %>    

<% } %>

后续步骤

现在,让我们看看如何使用 AJAX 使经过身份验证的用户能够回复晚餐邀请。