ASP.NET MVC - 安全性

在本章中,我们将讨论如何在应用程序中实现安全功能。 我们还将了解 ASP.NET 中包含的、可在 ASP.NET MVC 中使用的新成员资格功能。 在最新版本的 ASP.NET 中,我们可以通过以下方式管理用户身份 −

  • SQL 数据库
  • 本地 Windows 活动目录

在本章中,我们还将了解 ASP.NET 中的新身份组件,并了解如何为用户和角色自定义成员身份。

身份验证

用户认证是指验证用户的身份。 这一点非常重要。 出于显而易见的原因,您可能需要仅向经过身份验证的用户展示您的应用程序。

让我们创建一个新的 ASP.Net MVC 应用程序。

新 MVC 应用程序

单击"确定"继续。

当您启动新的 ASP.NET 应用程序时,该过程中的步骤之一是配置身份验证服务以满足应用程序的需要。

选择 MVC 模板,您将看到"更改身份验证"按钮现已启用。

身份验证按钮已启用

这是通过"新建项目"对话框中显示的"更改身份验证"按钮完成的。 默认身份验证是个人用户帐户。

身份验证选项

当您单击"更改"按钮时,您将看到一个包含四个选项的对话框,如下所示。

无身份验证

第一个选项是"无身份验证",当您想要构建一个不关心访问者是谁的网站时,可以使用此选项。

无身份验证

它对任何人开放,每个人都通过每个页面进行连接。 您以后可以随时更改该设置,但"无身份验证"选项意味着不会有任何功能来识别访问该网站的用户。

个人用户帐户

第二个选项是个人用户帐户,这是传统的基于表单的身份验证,用户可以在其中访问网站。 他们可以注册、创建登录名,并且默认情况下,他们的用户名使用一些新的 ASP.NET 身份功能存储在 SQL Server 数据库中,我们将对此进行介绍。

个人用户帐户

密码也存储在数据库中,但首先经过哈希处理。 由于密码经过哈希处理,因此您不必担心数据库中的纯文本密码。

此选项通常用于您想要建立用户身份的互联网站点。 除了让用户为您的网站创建带有密码的本地登录信息之外,您还可以启用来自 Microsoft、Google、Facebook 和 Twitter 等第三方的登录信息。

这允许用户使用他们的实时帐户或 Twitter 帐户登录您的网站,并且他们可以选择本地用户名,但您不需要存储任何密码。

这是我们将在本模块中花一些时间讨论的选项:个人用户帐户选项。

工作和学校帐户

第三个选项是使用组织帐户,这通常用于您将使用 Active Directory 联合服务的业务应用程序。

工作学校帐户

您将设置 Office 365 或使用 Azure Active Directory 服务,并且可以单点登录内部应用和云应用。

您还需要提供应用程序 ID,以便您的应用程序需要在 Windows Azure 管理门户中注册(如果该应用程序基于 Azure),并且应用程序 ID 将在所有可能注册的应用程序中唯一标识该应用程序。

Windows 身份验证

第四个选项是 Windows 身份验证,它非常适合 Intranet 应用程序。

Windows 身份验证

用户登录 Windows 桌面并可以启动浏览器访问位于同一防火墙内的应用程序。 ASP.NET 可以自动获取由活动目录建立的用户身份。 此选项不允许对站点进行任何匿名访问,但这又是一个可以更改的配置设置。

让我们看一下基于表单的身份验证,即名称为"个人用户帐户"的身份验证。 该应用程序将在本地 SQL Server 数据库中存储用户名和密码、旧密码,并且在创建该项目时,Visual Studio 还将添加 NuGet 包。

基于表单的身份验证

现在运行此应用程序,当您第一次访问此应用程序时,您将成为匿名用户。

匿名用户

您还没有可以登录的帐户,因此您需要在此网站上注册。

单击"注册"链接,您将看到以下视图。

点击注册链接

输入您的电子邮件 ID 和密码。

输入电子邮件 ID 密码

单击"注册"。 现在,应用程序将识别您。

点击注册

它将能够显示您的名字。 在下面的屏幕截图中,您可以看到 Hello,muhammad.waqas@outlook.com! 被展示。 您可以点击它,它是一个指向您可以更改密码的页面的链接。

显示您的名字

您还可以注销、关闭、重新启动,一周后返回,并且您应该能够使用之前使用的凭据登录。 现在点击注销按钮,将显示以下页面。

单击注销按钮

再次单击"登录"链接,您将转到以下页面。

点击登录链接

您可以使用相同的凭据再次登录。

为了实现这一目标,幕后需要进行大量工作。 然而,我们想要做的是检查每个功能并了解此 UI 是如何构建的。 什么是管理注销和登录过程? 这些信息在数据库中存储在哪里?

让我们从一些简单的基础知识开始。 首先我们来看看这个用户名是如何显示的。 从解决方案资源管理器的 View/Shared 文件夹中打开 _Layout.cshtml。

<!DOCTYPE html>
<html>
   <head>
      <meta charset = "utf-8" />
      <meta name = "viewport" content = "width = device-width, initial-scale = 1.0">
      <title>@ViewBag.Title - My ASP.NET Application</title>
      @Styles.Render("~/Content/css")
      @Scripts.Render("~/bundles/modernizr")
   </head>
	
   <body>
      <div class = "navbar navbar-inverse navbar-fixed-top">
         <div class = "container">
			
            <div class = "navbar-header">
               <button type = "button" class = "navbar-toggle" datatoggle = "collapse"
                  data-target = ".navbar-collapse">
                     <span class = "icon-bar"></span>
                     <span class = "icon-bar"></span>
                     <span class = "icon-bar"></span>
               </button>
					
               @Html.ActionLink("Application name", "Index", "Home", new
               { area = "" }, new { @class = "navbar-brand" })
            </div>
				
            <div class = "navbar-collapse collapse">
               <ul class = "nav navbar-nav">
                  <li>@Html.ActionLink("Home", "Index", "Home")</li>
                  <li>@Html.ActionLink("About", "About", "Home")</li>
                  <li>@Html.ActionLink("Contact", "Contact", "Home")</li>
               </ul>
					
               @Html.Partial("_LoginPartial")
            </div>
				
         </div>
			
      </div>
      <div class = "container body-content">
         @RenderBody()
         <hr />
         <footer>
            <p>© @DateTime.Now.Year - My ASP.NET Application</p>
         </footer>
      </div>
		
      @Scripts.Render("~/bundles/jquery")
      @Scripts.Render("~/bundles/bootstrap")
      @RenderSection("scripts", required: false)
		
   </body>
</html>

有一个通用的导航栏、应用程序名称、菜单,还有一个正在呈现的名为 _loginpartial 的部分视图。 这实际上是显示用户名或注册和登录名的视图。 因此 _loginpartial.cshtml 也在共享文件夹中。

@using Microsoft.AspNet.Identity
@if (Request.IsAuthenticated) {
   using (Html.BeginForm("LogOff", "Account", FormMethod.Post,
      new { id = "logoutForm", @class = "navbar-right" })){
         @Html.AntiForgeryToken()
         <ul class = "nav navbar-nav navbar-right">
            <li>
               @Html.ActionLink("Hello " + User.Identity.GetUserName() + "!",
               "Index", "Manage", routeValues: null, htmlAttributes: new { title = "Manage" })
            </li>
				
            <li>
               <a href = "javascript:document.getElementById('logoutForm').submit()">Logoff</a>
            </li>
				
         </ul>
      }
}else{
   <ul class = "nav navbar-nav navbar-right">
      <li>@Html.ActionLink("Register", "Register", "Account", routeValues:
         null, htmlAttributes: new { id = "registerLink" })</li>
			
      <li>@Html.ActionLink("Log in", "Login", "Account", routeValues: null,
         htmlAttributes: new { id = "loginLink" })</li>
   </ul>
}

正如您在上面看到的,有 if/else 语句。 如果我们不知道用户是谁,因为请求未经过身份验证,此视图将显示注册和登录链接。 用户可以点击链接进行登录或注册。 所有这些都是由帐户控制器完成的。

现在,我们想了解如何获取用户名,这在 Request.IsAuthenticated 中。 您可以看到对 User.Identity.GetUserName 的调用。 这将检索用户名,在本例中为"muhammad.waqas@outlook.com"

授权

假设我们有某种信息,希望防止未经身份验证的用户访问。 因此,让我们创建一个新的控制器来显示该信息,但仅在用户登录时显示。

右键单击控制器文件夹并选择 Add → Controller。

用户已登录

选择 MVC 5 控制器 - 空控制器,然后单击"添加"。

输入名称 SecretController 并单击"添加"按钮。

SecretController

它将有两个操作,如以下代码所示。

using System.Web.Mvc;

namespace MVCSecurityDemo.Controllers{
   public class SecretController : Controller{
      // GET: Secret
      public ContentResult Secret(){
         return Content("私密信息在这里");
      }
		
      public ContentResult PublicInfo(){
         return Content("公共信息在这里");
      }
   }
}

当您运行此应用程序时,您无需任何身份验证即可访问此信息,如以下屏幕截图所示。

私密信息在这里

因此,只有经过身份验证的用户才应该能够访问 Secret 操作方法,并且任何人都可以在没有任何身份验证的情况下使用 PublicInfo。

为了保护此特定操作并防止未经身份验证的用户到达此处,您可以使用授权属性。 不带任何其他参数的 Authorize 属性将确保用户的身份已知并且他们不是匿名用户。

// GET: Secret
[Authorize]
public ContentResult Secret(){
   return Content("私密信息在这里");
}

现在再次运行此应用程序并指定相同的 URL http://localhost:54232/Secret/Secret。 MVC 应用程序将检测到您无权访问该应用程序的特定区域,并将自动将您重定向到登录页面, 您将有机会登录并尝试返回到申请中被拒绝的区域。

重定向自动登录页面

您可以看到它是在返回 URL 中指定的,它本质上告诉此页面,如果用户登录成功,则将其重定向回 /secret/secret。

输入您的凭据并点击"登录"按钮。 您将看到它直接进入该页面。

私密信息在这里

如果您返回主页并注销,则无法进入秘密页面。 系统将再次要求您登录,但如果转到 /Secret/PublicInfo,即使您未通过身份验证,您也可以看到该页面。

公共信息在这里

因此,当您不想在几乎所有操作都需要授权的控制器内对每个操作进行授权时。 在这种情况下,您始终可以将此过滤器应用于控制器本身,现在该控制器内的每个操作都将要求用户进行身份验证。

using System.Web.Mvc;

namespace MVCSecurityDemo.Controllers{
   [Authorize]
   public class SecretController : Controller{
      // GET: Secret
      public ContentResult Secret(){
         return Content("私密信息在这里");
      }
		
      public ContentResult PublicInfo(){
         return Content("公共信息在这里");
      }
   }
}

但是如果您确实希望任何操作开放,您可以使用另一个属性覆盖此授权规则,即AllowAnonymous。

using System.Web.Mvc;

namespace MVCSecurityDemo.Controllers{
   [Authorize]
   public class SecretController : Controller{
      // GET: Secret
      public ContentResult Secret(){
         return Content("私密信息在这里");
      }
		
      [AllowAnonymous]
      public ContentResult PublicInfo(){
         return Content("公共信息在这里");
      }
   }
}

运行此应用程序,您可以通过登录访问 /Secret/PublicInfo,但其他操作将需要身份验证。

公共信息在这里

它将仅允许匿名用户执行此操作。

使用 Authorize 属性,您还可以指定一些参数,例如允许某些特定用户执行此操作。

using System.Web.Mvc;

namespace MVCSecurityDemo.Controllers{
   [Authorize(Users = "ali.khan@outlook.com")]
   public class SecretController : Controller{
      // GET: Secret
      public ContentResult Secret(){
         return Content("私密信息在这里");
      }
		
      [AllowAnonymous]
      public ContentResult PublicInfo(){
         return Content("公共信息在这里");
      }
   }
}

当您运行此应用程序并转到 /secret/secret 时,它会要求您登录,因为它不是此控制器的正确用户。

转到私密信息