• 使用 ASP.NET 2.0 增强网站的安全性


    本文以 2004 年 3 月社区技术预览中的 ASP.NET 2.0 内容为基础。文中包含的所有信息有可能变更。

    本文讨论:

    ASP.NET 2.0 中的安全性增强

    服务器端安全性控制

    用户和角色数据库

    无 cookie 的窗体身份验证

    本文使用下列技术:

    ASP.NET、身份验证

    *
    本页内容
    推进窗体身份验证 推进窗体身份验证
    入门 入门
    服务器端安全控件 服务器端安全控件
    定义角色 定义角色
    密码恢复 密码恢复
    调整提供程序 调整提供程序
    控件调整 控件调整
    成员和角色编程 成员和角色编程
    无 cookie 的窗体身份验证 无 cookie 的窗体身份验证
    一些预防措施 一些预防措施
    小结 小结

    新的安全功能是 ASP.NET 2.0 中的一项重要改进。这些功能包括管理用户帐户数据库的成员身份服务、哈希密码、管理用户角色成员身份的角色管理器,以及可以更容易实现窗体身份验证的五个新的服务器端控件。ASP.NET 2.0 还提供了提供程序模型,使您能够完全地控制 Membership 和 Role 服务以及无 cookie 窗体身份验证的实现。您还可以非常容易的对用户帐户和角色进行基于 Web 的本地和远程管理,并且可以获得对其他非安全性相关设置的增强控制。

    推进窗体身份验证

    窗体身份验证是 ASP.NET 1.0 中最广泛使用的功能之一,因为它封装了许多特定实现所缺少的最佳实践。例如,您知道有多少窗体身份验证实现可以保护用来存放客户端凭证的 cookie 的完整性?窗体身份验证不仅将用户名写入该 cookie 中,而且还添加了一个消息身份验证代码(一个根据该 cookie 形成的哈希值和只有 Web 服务器知道的秘密值)。这使得恶意的客户端不能提高特权或通过修改其 cookie 中的名称来查看另一个用户的数据。如果您留意 .NET Web 开发人员推出的各种新闻组和列表服务器,您将了解到人们正一遍又一遍地实现相同的东西:用户数据库、cookie 中缓存的角色、捕获用户名和密码的控件、管理用户和角色的工具。ASP.NET 组已经提供了针对几乎所有这些问题的内置解决方案。在研究 ASP.NET 2.0 的最初测试版构建时,让我感到震惊的是,它彻底减少了用来构建以可管理的方式使用窗体身份验证的网站的代码的数量。

    入门

    当我带您完成一些试验时,您将看到使用这些新功能起步是多么容易。如果您有 ASP.NET 2.0 内部测试版(MSDN Universal 订户可以下载),您就可以进行这些试验。

    首先,需要有一个指向空目录的虚拟目录。必须确保 ASP.NET 辅助进程具有读取、执行和写入这个目录的权限。如果正在运行 Windows 2000 或 Windows XP,就需要将这些权限授予 ASPNET 本地帐户,而在 Windows Server 2003 下,则需要将权限授予 Network Service 帐户。

    我将使用窗体身份验证,因此我需要通过 web.config 文件来启用它。如果我现在向您展示如何使用 ASP.NET 1.1,我会告诉您打开一个文本编辑器并开始手动键入 XML。但是在 ASP.NET 2.0 中,我最喜欢的功能之一是交互式配置文件编辑器,它直接构建于 IIS 管理控制台,您可以在虚拟目录的属性表的"ASP.NET"选项卡上找到。按下"Edit configuration"按纽,将会弹出该编辑器。


    1 配置编辑器

    图 1 显示了这个新的编辑器。您会看到我选择了窗体身份验证而不是默认选项:Windows 身份验证。在您自己的虚拟目录中进行同样的操作。当使用配置工具时,将 Web 应用程序的默认语言设置为 C#,因为它将替您省去一些后面需要进行的输入。Page Language Default 设置是 Application 选项卡上的第一个下拉选项。在应用这些更改之后,您将在目录中找到 web.config 文件,并带有所有设置。

    您需要向 Membership 服务注册一些用户以便开始,因此您写的第一页是允许您添加用户的页。在该测试版中提供有一个服务器控件,通过该控件,您可以使用下面三行代码来实现该页:

    <form runat='server'>
    <asp:createuser runat='server'/>
    </form>
    

    不过,由于我使用的是最初内部测试版,所以我必须直接使用 Membership 类来手工为这个特殊的窗体编写代码。而现在,只需使用图 2 中所示的 ASPX 页就可以了,我将在本文稍后讨论 Membership 类。图 3 显示当您将浏览器指向该页时所看到的内容。继续进行试验,现在添加一些用户和密码。您的工作应该轻松得多,因为这做得超常的好!


    3 Membership 页

    在添加完用户之后,请仔细查看虚拟目录。您应该看到一个新的名为"DATA"的子目录,其中有一个 Microsoft Access 数据库。这是 Membership 和 Role 服务默认存储其数据的地方,但是稍后我会向您展示如何覆盖默认存储机制以使用 SQL Server,或您自己的自定义数据储备库。现在是使用 ASP.NET 2.0 中提供的安全控件的时候了。

    服务器端安全控件

    图 4 列出了 ASP.NET 2.0 中提供的五个新的安全控件。从 LoginStatus 控件开始探索是个好主意。首先创建一个包含该控件的新 ASPX 页。为了简单起见,调用新页面 default.aspx:

    <form runat='server'>
    <asp:loginstatus runat='server'/>
    </form>
    

    将浏览器指向该页面,您应该看到一个 Login 链接。如果您在浏览器中查看结果页面的源代码,您将看到这个超级链接指向一个名为 login.aspx 的页面,而您还没有编写它。这又是一个用三行代码实现的 Web 页,因此我们继续进行试验,现在就创建它:

    <form runat='server'>
    <asp:login runat='server'/>
    </form>
    

    如果您曾经手工实现过窗体身份验证,您就会赞赏这三行代码。过去,执行数据库查找的等同实现需要两倍数量的代码。

    现在回到您的浏览器,并单击 Login 链接,它会把您带到 图 5 中所示的登录页面。尝试用一个无效用户名或密码登录,可以发现,系统会弹出一条适当的默认错误消息。这条消息不会给攻击者太多的信息。而一个没有经验的开发人员也决不会无意中发送回一条消息给该用户,告诉他获得了正确的用户名,请尝试猜测另一个密码!


    5 登录页面

    继续进行试验,键入有效的用户名和密码 - 前面您通过 adduser.aspx page 页面输入的用户名和密码 - 您应该重新定向回 default.aspx 页面。由于您没有为登录控件提供任何自定义操作,所以默认情况下它只通过窗体身份验证来让您登录,这意味着您的浏览器现在有了一个存放用户名的加密 cookie。

    既然您已经重定向回 default.aspx 页面,您看到什么不同吗?登录状态控件现在应该显示 Logout 而不是 Login。因为窗体身份验证 cookie 是与请求一起发送的,所以 FormsAuthenticationModule 创建了一个经过身份验证的用户主体,并且将其与该请求的上下文相关联。登录状态控件会注意到这种情况,并且改变成允许您注销。尝试注销并重新登录来查看这项工作。

    现在,让我们再添加一些代码到 default.aspx 页面:

    <h3>User Name: <%= User.Identity.Name %>
    </h3>
    <h3>User Type: <%= User.GetType() %></h3>
    

    刷新这个页面,您应该看到您用来登录的用户名。注意,表示用户的基本对象是 GenericPrincipal 类型,这是 FormsAuthenticationModule 表示用户的方式。一旦您启动 Role Manager,您就会注意到这种类型变化,因为当启用时,新的 RoleManagerModule 就取代了由 FormsAuthentication 使用它自己的类型生成的用户。

    现在,让我们添加一个 LoginView 控件到 default.aspx 页面来显示可以根据用户的登录而改变的内容。使用这个控件最简单的方法是提供两个内容块:一个用于匿名请求(在用户登录之前),另一个用于身份验证请求(在用户登录之后):

    <asp:loginview runat='server'>
    <anonymoustemplate>
    <h4>If you see this, you've not yet logged in!</h4>
    </anonymoustemplate>
    <loggedintemplate>
    <h4>Welcome to my website, <asp:loginname runat='server'/>!</h4>
    </loggedintemplate>
    </asp:loginview>
    

    当您登录或注销时,您应该看到 LoginView 控件中的文本发生了改变,正如我们所预料的一样。这是一个非常简单的想法,但是它确实让您的代码变得清晰了许多。

    定义角色

    我已经制作了一个简单的页面,它允许您使用 Role Manager 将用户添加到角色,但是在您能够使用它之前,您需要为您的应用程序启用 Role Manager。回到配置工具,并找到 Authentication 选项卡。选中标有"Role management enabled"的复选框,然后应用这个改变。

    addrole.aspx 页面的代码显示在图 6 中,而图 7 显示了窗体的外观。将这个页面放在您的虚拟目录中,并且将您的浏览器指向它,这样您就可以添加一些角色了。指定一个用户名(您前面通过 adduser.aspx 窗体添加的用户名)及一个角色名,然后按一下按纽将用户添加到角色中。代码首先添加角色(如果它不存在的话),然后将用户添加到角色。在幕后,Role Manager 会在 Membership 服务使用的同一 Microsoft Access 数据库中跟踪这些角色映射,但是这实际上只是巧合。Role Manager 可以将其数据存储在 SQL Server 或任何其他的存储中,并且不必使用与 Membership 服务相同的机制。为了支持这一点,Membership 和 Role Manager 的提供程序模型完全不同。

    a

    7 添加角色

    如果您曾经在 ASP.NET 中实现过自定义角色,您就会赞赏内置的 Role Manager,因为您不必再为了实现基于角色的安全性而成为 ASP.NET HTTP 管道的主人。一旦您添加了一些角色,您就可以回到 default.aspx,并且可以使用 LoginView 控件来做一些有趣的事情。在 元素之后添加另一个部分:

    <rolegroups>
    <asp:rolegroup roles='ForumModerators'>
    <contenttemplate>
    <h4>Controls for forum moderators
    go here.</h4>
    </contenttemplate>
    </asp:rolegroup>
    <asp:rolegroup roles='Friends'>
    <contenttemplate>
    <h4>Welcome, friend!</h4>
    </contenttemplate>
    </asp:rolegroup>
    </rolegroups>
    

    您可能没有选择与我相同的角色,因此您将需要用您自己的角色名来代替我的角色名,并且调整内容使之适合角色。一旦您完成了,就可以通过使用不同角色中的不同用户帐户登录来检验您的新页面,并且观察当角色改变时页面的内容如何改变。注意,如果两个角色组都与用户的角色相匹配,则总是显示第一个匹配的角色组(从上到下)。

    虽然这并不新鲜,但是请您记住,您总可以通过 User.IsInRole 以编程方式测试角色。还需要谨记的是,您可以使用 web.config 中的 部分来准许或拒绝访问各个页面,如下所示:

    <authorization>
    <deny users='?'/>
    <allow roles='ForumModerators'/>
    <deny users='*'/>
    </authorization>
    

    第一项告诉 ASP.NET 禁止传入任何未经身份验证的请求(强制执行身份验证)。第二项和第三项确保只有 ForumModerators 可以访问 web.config 文件所驻留的目录树中的内容。记住,授权部分可用于子目录中的 web.config 文件,也可以用于<location/> 元素,以控制对单独文件的访问。

    密码恢复

    在这个介绍演示中,我还没有向您展示密码恢复控件,因为对它的使用需要进行仔细的考虑。您可能知道这个控件的作用:它让用户请求通过电子邮件将他的密码发送给他。在决定将明文密码通过电子邮件发送给用户之前,您需要做一些风险评估。

    事实上,如果您把这个控件放在您现有站点的一个页面上,它将不会起作用,因为在默认情况下,Membership 服务会拒绝公开明文密码。即使它想这样也是不可能的,因为在默认情况下它只存储密码的单向哈希值而不存储密码本身。当要求验证密码时,Membership 服务会哈希所提交的密码,并将该哈希值与其副本相比较。如果您想要恢复明文密码,您可以将 Membership 提供程序重新配置为以加密的形式存储密码,在这种情况下,Membership 提供程序将使用<machineKey/> 来加密密码。这样就可以对密码进行解密并通过电子邮件发送给用户。

    如果您存储哈希密码(这真是一个好主意),您就需要准备一种替换的方法来对用户进行身份验证。您不能通过电子邮件将密码发送给用户,但是如果您提前问了几个问题,比如"您最喜欢的宠物叫什么名字?",您就可以使用这些答案来对用户进行身份验证,并允许他向您发送一个新的密码。然而,Membership 服务并不支持为每个用户保留问题和答案,使用它仅仅是为了决定是否可以通过电子邮件发送密码,因此它不能与哈希密码一起使用。据我看来,这方面将耗费一些工作。

    Building Secure Software (Addison-Wesley, 2002) 的 95 页上,Viega 和 McGraw 提出了一种通过问与答来重新设置密码的好模型。这种模型需要使用数百个问题的集合,当用户首次设置她的帐户时,它会随机挑出一组问题来问用户。如果用户请求重新设置密码,您就可以问她这些问题中的一部分。这需要她正确的回答许多质询问题以便继续进行操作。如果用户成功地回答了所有的问题,您还可以选择一组新的随机问题代替前面使用的问题来进行提问。

    调整提供程序

    到目前为止,我特意使用默认设置来保持它的简单,但是您需要调整这些设置以适合您自己的环境。例如,如果您想让 Membership 服务将其数据存储在 SQL Server 中,您就应该选择 AspNetSqlProvider 而不是默认的 AspNetAccessProvider。这个设置在配置工具的 Authentication 页面。

    但是如果您已经有了一个需要集成的现有用户数据库,该怎么办?它肯定不会有 AspNetSqlProvider 需要的表和列。另外,如果它在 AS/400 服务器或 Oracle 安装上又该怎么办?幸运的是,Membership 和 Role Manager 系统都构建在分层模型上,我已经在图 8 中显示了这个模型。您可以通过扩展定义在 System.Web.Security 命名空间中的抽象 MembershipProvider 类来完全代替 Membership 数据存储。类似地,您可以通过扩展 RoleProvider 来代替 Role Manager 数据存储。Rob Howard 在他的"'Nothin' But ASP.NET"专栏中更详细地讨论了提供程序模型。


    8 提供程序模型

    的确,使用现有的提供程序是最容易的。在最初测试版中,有两个模型。一个与 Access 数据库协同工作,正如您所看到的,它运行得超常的好。另一个是我先前提到的 SQL Server 提供程序。到测试版时,应该还有针对 Active Directory 来验证用户的 Membership 提供程序,以及从 Authorization Manager 中查找角色的 Role 提供程序。

    即使您选择了一个内置提供程序,您还可以在 web.config 中调整它的行为。图 9 显示了 SQL Server Membership 提供程序的提供程序设置。注意 passwordFormat 设置,其中,您可以在三个选项之间进行选择:Hashed(默认)、Encrypted 和 Clear。然后,您可以通过 enablePasswordRetrieval 和 requiresQuestionAndAnswer 属性来选择密码恢复策略。当然,假如您选择使用哈希密码,您就必须将 enablePasswordRetrieval 设置为 false。否则,您也可以要求用户在系统通过电子邮件发送他的密码之前回答一个质询问题。


    9 提供程序设置

    数据库的连接字符串并没有存储在您的 web.config 文件中,而是直接引用的。注意,此属性称为 connectionStringName,并且指向专门设计用来存放连接字符串的 machine.config 部分。将连接字符串保存在 web.config 文件之外是一个好主意,在您不能使用集成的身份验证而不得不使用密码时尤其如此。ASP.NET 2.0 预定支持对配置文件的敏感部分进行 XML 加密,对于 machine.config 中的连接字符串部分来说,这实在是一个方便的功能。

    Role Manager 可以配置为使用 cookies 或 URL munging,并且可以将角色缓存在 cookie 中,从而减少往返角色数据库的次数。该缓存是智能的:如果缓存角色的数目开始变得很大,Role Manager 将在 cookie 中缓存最近使用的角色,而动态地查找使用最少的角色。这种功能可能是由于需要用有限的存储空间来支持移动设备而激发的。

    还可以调整许多其他的设置,但是我准备把它们留给您自己去研究。同时,让我们看一看可用来调整前面使用的服务器端安全控件的方法。

    控件调整

    用三行代码就能够创建一个登录页面是十分简洁的,但是一般来说,您需要对登录控件进行一些自定义以适合您的应用程序。图 10 显示了一些代码,您可以用这些代码来代替前面创建的简单登录页面。另外,您还可以用您期望 Web 控件具有的属性来修改这些控件的外观。而通过 ASP.NET 2.0 中的主题支持,您不必更改代码就可以在整个网站中维持一致的外观。

    登录控件的一个有趣的功能是,它不必像我在这个例子中所做的一样固定在自己的页面。相反,您可以将其作为主页面的一部分,这样它就会始终出现在页边空白处。一旦用户登录,实际上您就不想再看到它,因此在默认情况下,当它检测到经过身份验证的用户已经存在时,它就会消失。您可以通过 VisibleWhenLoggedIn 属性来调整这种行为。这是开发人员使用 ASP.NET 1.1 手动实现该功能的一个例子,现在它内置在 ASP.NET 2.0 中。

    其他的控件有类似的选项。例如,如果您希望为用户登录或注销显示一个漂亮的按钮,则可以在登录状态控件上设置 Login(Out)ImageUrl 属性。

    为了感受一下它的工作方式,可以使用 Visual Studio 2005 项目向导来创建一个 Internet 网站。对于本文,只有在您将"Web.vssettings"IDE 设置文件导入 Visual Studio 的情况下才会显示这个向导。您可以通过 Tools-Import/Export Settings 对话框来做到这一点。该向导包括到此为止所谈到的全部功能,并且提供了丰富的 UI 自定义,以获得您渴望新的网站具有的外观和功能。

    成员和角色编程

    当您希望远离服务器端安全控件时,最好知道您也可以直接使用实现这个高级功能的类。为了学习这些服务的编程模型,您需要分析两个主类:Membership 和 Roles。由于文章的篇幅所限,我无法在这里详尽地介绍它们,而在产品向最终版本发展的过程中,其中的一些细节肯定会改变,不过,让我首先带您体验一下。

    从 Membership 类中,您可以创建和管理用户,其中每一个用户都是由 MembershipUser 类的一个实例表示的。这个类表示用户配置文件,包括诸如 Email、CreationDate、PasswordQuestion 等属性。当您创建和更新这些用户配置文件时,您可以通过 Membership 类来这样做,因为它是分层模型,隐藏了存储配置文件的位置和方式的细节(请参见图 8)。此类提供了更改用户密码和将密码重新设置为一个计算机生成的随机密码的方法,这是一个时间戳,它跟踪用户的活动,从而维护当前用户的数量(您可以通过调用 Membership 类中的 GetNumberOfUsersOnline 方法来获取这个数目)。

    要验证一个用户密码,只需调用 Membership 类中的 ValidateUser 方法并传入用户名和密码就可以了。基本提供程序将负责所有必要的密码哈希和解密。如果用户忘记它的用户名,可以通过要求他提供一个电子邮件地址并且将其传送给 GetUserNameByEmail 方法来提醒他,但这不是一个安全的选择。

    无 cookie 的窗体身份验证

    当我教授 ASP.NET 窗体身份验证时遇到最多的抱怨之一就是它需要 cookie。幸运的是,在 ASP.NET 2.0 中没有这种限制。web.config 中的 元素上有一个新的"cookieless"属性。您可以将此属性设置为以下四个值之一:UseCookies、UseUri、UseDeviceProfile 或 AutoDetect。

    UseCookies 和 UseUri 分别强制要求 FormsAuthenticationModule 对所有的请求使用 cookies 或者 URL munging。UseDeviceProfile 用于查看浏览器功能以确定使用哪种模式。最后,AutoDetect 将尝试设置 cookie,而如果失败,就将改为使用 URL munging。典型的 URL 在保护之后如下所示(省略号是我添加的,因为这些 URL 可能很长):http://www.acme.com/foo/(F(Cvc...A1))/default.aspx。

    URL 中括号内的部分包含 cookie 通常包含的数据,并且将被 HTTP 管道中的模块取消,因此如果您从 ASPX 页面读取 Request.Path 属性,您将不会在 URL 中看到任何多余的内容。如果您重定向请求,URL 会自动被保护。换句话说,这段代码将(正确地)带您回到您当前正在查看的页面(在 URL 被正确保护的情况下):

    Response.Redirect(Request.Path)
    

    这种功能应该使窗体身份验证在很大程度上得以更广泛地实现。然而,随着使用 ASP.NET 窗体身份验证的网站不断增多,越来越多的攻击者会试图发现弱点,因此遵守一些基本规则是一个好注意。

    一些预防措施

    如果不通过安全套接字层 (SSL) 进行保护,窗体身份验证并不非常强大。至少应该通过安全连接将您的登录页面发送给用户并传送回 Web 服务器,以防止窃听者窃取用户的明文密码。但通常这样做并不够。由于 cookies 工作方式的缘故,窃取窗体身份验证 cookie 的窃贼已经窃取了登录信息,因而无法实现重播检测。记住,cookie 通常与每个请求一起发送,甚至对于像请求页面上按钮的 GIF 文件这样简单的事情也是如此。一旦被盗,攻击者就可以使用该 cookie 来模仿用户。为了减少这种风险,您需要大大缩短 cookie 超时,或者通过 SSL 运行网站的整个部分(或者还更好一些,整个网络)。

    对于需要高安全性的站点,我更愿意选择后者,当人们抱怨 SSL 运行缓慢时,我问他们为什么不去购买硬件来加速它。然而,一些公司仅仅在部分站点中坚持使用 SSL。如果您是这种情况,您可以通过启用 元素中的 requireSSL 属性来减少这些 cookie 重播攻击。这会将"Secure"属性添加到窗体身份验证 cookie,它指示浏览器应该仅通过安全通道将 cookie 发送回服务器。换句话说,它不会与不通过 SSL 运行的请求一起发送。这种功能是在 .NET 框架 的 1.1 版中增加的,因而不是 ASP.NET 2.0 所独有的。ASP.NET 2.0 中新增的特性是,这种对策还可以应用于会话 cookie:

    <httpCookies requireSSL='true' httpOnlyCookies='true'/>
    

    由于安全的 cookie 不会与不通过 SSL 运行的请求一起发送,所以对于可以通过原始 HTTP 进行访问的页面,您可以肯定 User.Identity.IsAuthenticated 每次都会返回 false。换句话说,您将不知道那些在任意页面上不通过 SSL 运行的用户是谁。注意,即使您决定通过 SSL 来运行整个站点,在您偶然允许通过原始 HTTP 访问一两个文件的情况下,启用 requireSSL 属性确实是一个好主意。

    作为防止跨站点脚本攻击的措施,httpOnlyCookies 属性是非常有用的;它指示浏览器不应该从脚本访问 cookie。它使用一个名为 HttpOnly 的 cookie 属性,目前只有新版的 Internet Explorer 才能识别它,但这是个很好的主意,我希望其他的浏览器厂商采用它。要学习更多的知识,请参阅 Some Bad News and Some Good News

  • 相关阅读:
    二叉树中序遍历
    前序遍历
    配置免秘钥登录
    pip安装
    zookeeper C client API 和zkpython 的安装
    安装 Java
    json递归查询
    php 编译安装
    docker 中 安装配置 mysqlcluster(arm)
    glibc编译安装
  • 原文地址:https://www.cnblogs.com/ywqu/p/1311506.html
Copyright © 2020-2023  润新知