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

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。 我们还希望添加授权逻辑,以便只有托管晚宴的用户才能编辑该逻辑,并拒绝所有其他用户的编辑访问权限。

Forms 身份验证和 AccountController

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

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

“书呆子晚宴主办晚餐”页面的屏幕截图。右上角突出显示了“登录”。

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

“书呆子晚餐登录”页的屏幕截图。

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

“书呆子晚餐创建新帐户”页面的屏幕截图。

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

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

“书呆子晚宴主办晚餐”窗体页的屏幕截图。右上角突出显示了“欢迎”和“注销”按钮。

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

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

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

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

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

Nerd Dinner 导航树的屏幕截图。应用数据已展开,并选择了“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 应用程序服务的适当架构添加到数据库。

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

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

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

实现此逻辑非常简单。 只需将 [Authorize] filter 属性添加到 Create 操作方法,如下所示:

//
// GET: /Dinners/Create

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

//
// POST: /Dinners/Create

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

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

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

[Authorize] 筛选器(可选)支持指定“用户”或“角色”属性的功能,这些属性可用于要求用户既登录又是允许的用户列表或允许的安全角色的成员。 例如,下面的代码仅允许两个特定用户“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));
}

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

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

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

为了帮助实现此目的,我们将首先将“IsHostedBy (username) ”帮助程序方法添加到之前) 生成的 Dinner.cs 分部类中的 Dinner 对象 (。 此帮助程序方法返回 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 (用户名) 帮助程序方法的 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 () 操作方法重复相同的步骤,以锁定删除 Dinners 的权限,并确保只有 Dinner 的主机才能删除它。

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

“书呆子晚餐”页面的屏幕截图。“编辑”和“删除”按钮在底部圈出。详细信息 U R L 在顶部圈出。

目前,我们将显示“编辑”和“删除”操作链接,无论详细信息 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 为经过身份验证的用户启用 RSVP 晚餐。