65.9K
CodeProject 正在变化。 阅读更多。
Home

SimpleMembershipProvider 与 MembershipProvider

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.86/5 (16投票s)

2013年8月14日

CPOL

6分钟阅读

viewsIcon

47587

您想了解的关于 SimpleMembershipProvider 为何不能作为 MembershipProvider 使用的一切。

引言

这篇文章实际上应该叫做“您想了解的关于 SimpleMembershipProvider 为何不能作为 MembershipProvider 使用的一切”。

永远不要相信名字中带有“简单”一词的类,它会设定通常无法满足的期望,而 SimpleMembershipProvider 也不例外。这个新的提供程序可能继承自 MembershipProvider 抽象类,但由于以下原因,它肯定不能与旧的成员系统一起使用:

  • 简单提供程序必须显式/隐式配置为默认提供程序
  • MembershipUser 未完全实现,并且只有 UserId/Username 属性被映射
  • 基于成员资格的 IsApproved/IsLockedOut 函数不受支持/未映射到新 API
  • 在账户被锁定时,Membership 的 ValidateUser 返回 true
  • 成员资格的核心功能将导致 NotSupportedException

对于大多数项目来说,这些问题可能并不重要,但有时这会成为一个问题。例如,当您需要利用提供程序的插件特性来集成到第三方系统(例如 CMS)时。

我自己的经验与使用 SimpleMembershipProvider 和 Sitecore 有关。我们希望在前台利用简单提供程序的新功能,但仍然在后台使用 Sitecore 的用户管理器功能。好消息是这些问题可以克服,在后面的文章中,我将开源我自己的替代提供程序。

背景

SimpleMembershipProviderWebMatrix 团队引入,被誉为 ASP.NET 成员资格的未来。实际上,成员资格很快将被 ASP.NET Identity 取代。与此同时,简单提供程序摆脱了 Membership/Roles 接口,引入了更简洁的 WebSecurity API。这样做打破了传统的基于用户名/密码的认证方案,并引入了对联合认证的支持。

新的 WebSecurity 框架还与 Entity Framework 结合,提供可扩展的实体模型,支持 Code-First,甚至可以绑定到现有的用户表/模式。从系统中移除存储过程意味着支持所有基于 SQL Server 的产品,包括 Azure 和 Sql Server CE。

值得注意的是,新版通用提供程序也支持扩展的 SQL Server 产品,但它们缺乏上述进一步的增强功能,并且只在预定义的成员资格模式上工作。

要查看 SimpleMembershipProvider 的实际应用,请打开 Visual Studio 并创建一个 MVC 4 Internet 应用程序模板的新实例。您会注意到 AccountController 专门使用新的 WebSecurity 类,该类又利用了 SimpleMembershipProvider 所派生自的新 ExtendedMembershipProvider 定义。

您可能还会期望在 Web.Configmembership 部分看到 SimpleMembershipProvider 注册,然而事实并非如此。

简单的魔法

这个所谓的简单 SimpleMembershipProvider 带着一定程度的魔力。哎……为什么不呢?可以通过 Web.Config 显式注册提供程序,但新的 WebSecurity API 也可能在幕后进行一些自动绑定。这由 enableSimpleMembership appSetting 和 WebSecurity.InitializeDatabaseConnection 例程控制。

无论 SimpleMembershipProvider 如何注册,任何在未首先调用 WebSecurity.InitializeDatabaseConnection 的情况下尝试使用 SimpleMembershipProvider 的行为都将导致以下异常:

You must call the "WebSecurity.InitializeDatabaseConnection" method before you call any other method of the "WebSecurity" class. This call should be placed in an _AppStart.cshtml file in the root of your site.

必须初始化提供程序的原因是应用程序可以映射到任何自定义数据库架构,并使 Entity Framework 能够为 Code-First 初始化数据库。

如果 Web.ConfigenableSimpleMembership 设置为 true,那么 WebSecurity 将使用 SimpleMembershipProviderSimpleRoleProvider 覆盖任何默认提供程序。

如果未设置 enableSimpleMembership,则 WebSecurity 将尝试初始化通过 Web.Config 配置的任何现有 ExtendedMembershipProviderExtendedRoleProvider 实例,但仅限于它们被配置为默认提供程序的情况。

如果 enableSimpleMembership 已设置,但未调用 WebSecurity.InitializeDatabaseConnection,则简单提供程序将包装并替换任何现有的 AspNetSqlMembershipProvider/AspNetSqlRoleProvider 实例。在这种情况下,WebSecurity API 上可用的扩展功能将导致未初始化异常。对旧版成员资格 API 的调用将传递给被包装的 AspNetSql 提供程序。

这导致了第一个问题

简单提供程序必须显式/隐式配置为默认提供程序。

在大多数情况下这不是问题,但对于像 Sitecore 这样可以同时使用多个提供程序的系统来说,这是一个真正的障碍。

兼容性

下一个问题是与旧会员系统的兼容性。为了支持 WebSecurity,新系统需要一个继承自 MembershipProviderExtendedMembershipProvider 实例。然而,尽管简单提供程序本身就是一个 MembershipProvider,但如果以这种方式使用,旧的 API 会在某种程度上出现问题。

其原因在于许多旧的成员资格数据库列已被移除,例如 LastLoginDate。此外,WebSecurity 支持一种新的基于令牌的账户确认功能,并引入了与旧系统不兼容的新密码锁定行为。

这些更改的后果是,使用旧版 API 时返回的 MembershipUser 现在仅支持 UserId/UserName。其他属性,如 Email、IsApproved、CreatedDate、IsLockedOut,都被硬编码为返回默认值。如果按照设计使用,这不是问题,因为新系统依赖 Entity Framework 模型而不是 MembershipUser 来向应用程序公开数据。只有当 SimpleMembershipProviderMembershipProvider 的上下文中被使用时,功能才会丢失。例如,解锁锁定账户的能力,甚至创建新用户账户的能力。这些场景现在只能通过 WebSecurity API 实现,并且将提供程序插入为旧系统编写的现有用户管理界面将无法工作。

登录请注意

与上述兼容性问题相关,在 MembershipProvider 的上下文中使用了 SimpleMembershipProvider 时,还会产生额外的后果。

由于密码锁定功能现在已重新设计为基于超时和最大重试计数,这意味着旧系统无法查看这些状态。因此,即使帐户被锁定,ValidateUser 方法现在也会返回成功。这实际上与新的 API 保持一致,它将检查锁定状态的责任转移到应用程序,但是当它插入到围绕原始成员资格系统构建的代码中时,肯定会引入安全漏洞。

顺便说一下,MVC 4 互联网应用程序模板在登录时缺少锁定账户检查。这可以按如下方式添加:

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model, string returnUrl)
{
    if (!ModelState.IsValid) 
    {
        ModelState.AddModelError("",
           "The user name or password provided is incorrect.");
    }
    else if (WebSecurity.IsAccountLockedOut(model.UserName, 10, 3600))
    {
        ModelState.AddModelError("", 
           "You have entered a password incorrectly too many times. Try again in an hour.");
    }

    if (ModelState.IsValid && WebSecurity.Login(
          model.UserName, model.Password, persistCookie: model.RememberMe))
    {
        return RedirectToLocal(returnUrl);
    }

    return View(model);
}

缺少支持

最后,简单的提供程序不支持许多旧的 API,如果调用这些 API,将返回 NotSupportedException。据推测,这些功能在没有存储过程的情况下太难实现,或者不符合新设计的新模式/方法。不受支持的 MembershipProvider 功能有:

  • CreateUser
  • GetUser (按 providerUserKey)
  • GetUserNameByEmail
  • FindUserByUserName
  • FindUserByEmail
  • GetAllUsers
  • FindUsersByName
  • FindUsersByEmail
  • UnlockUser
  • ChangePasswordQuestionAndAnswer
  • GetNumberOfUsersOnline
  • GetPassword
  • ResetPassword

结论

表面上看,SimpleMembershipProvider 通过扩展现有 MembershipProvider 提供了两全其美的方案。然而,实际上这种关系并没有提供任何有价值的向后兼容性,如果在此场景中使用,很容易引入安全漏洞。

在未来的一篇文章中,我将提供一套更兼容的提供程序,解决本文中提出的一些问题,并希望能弥合接口之间的差距。

© . All rights reserved.