具有租户特定角色的ASP.NET多租户应用程序

我们有一个多租户ASP.NET应用程序。 到目前为止,租户已经相互隔离,但现在我们有代理商管理多个租户,并希望能够通过一个用户帐户管理所有租户。 我正试图找出实现这一目标的最佳方法,希望对我们正在使用的现有技术没有太大的改变。

相关技术细节:

  • 成员和角色的AspNetSqlMembershipProvider
  • C#4.0(很快将是4.5)
  • 表单身份validation
  • aspx和MVC(v3)页面
  • 假设有100个或更多租户,因此任何解决方案都需要支持

我相信这些要求与SQL Server的安全模型非常相似。 我们有一组登录,代表可以登录系统的所有用户。 用户应该能够被赋予一个或多个数据库(租户)的角色。 示例:用户Bob在公司A中具有管理员角色,但在公司B中只有用户角色。我们公司的员工也有“sysadmin”角色,允许我们访问任何租户以及创建/删除租户等专业管理权限等

我已经对各种库,框架等进行了大量研究,但我没有找到任何令人信服的证据表明其他库或框架将比我们现有的更好。 所以我现在正在考虑如何让Sql Membership provider做我想做的事情,除非有人能指出我更好的方向。 我也不确定我是否知道在此搜索的最佳条款。

我有2个选项我正在考虑:

  1. 只向会员提供者添加少量角色,并处理会员提供者之外的“当前用户在此租户中是否具有此角色”的所有问题。 成员资格提供程序将用于处理对系统的基本访问。
  2. 将租户特定角色添加到成员资格提供者。 我们将拥有(角色数)x(租户数)在系统中的总角色。 每个新租户都会向系统添加另一组角色,例如“租户A:管理员”,“租户A:用户”等。需要一些额外的表来管理关系以及可能需要一些自定义代码以确保访问权限从成员资格提供者请求正确的特定于租户的角色。

这些选项中的任何一个都不错吗 或者我应该到别处寻找支持吗?

我不认为你能够将多租户安装到任何开箱即用的角色提供程序中,所以你不妨继续使用SqlMembershipProvider(和SqlRoleProvider)。 即使是最新的Microsoft.AspNet.Identity仍然假设用户和角色之间存在多对多的普遍现象。 你真正需要的是将第3列添加到多对多表的主键中,该表将标记您的租户,即:

user: 6 role: 4 tenant: 17 user: 6 role: 9 tenant: 18 (and so on) 

…通过这种方式,您可以让用户拥有不同租约的不同权限,所有这些都使用相同的角色名称集。

如果你使用选项#2,那么你的[授权]属性会爆炸。 想象一下:

 [Authorize(Roles = "TenantA:Admin", "TenantB:Admin", ...)] public ActionResult Post(int id, SomeViewModel model) {} 

…所有这些属性都必须在编译时编写,除非你使用自定义的AuthorizeAttribute,你可以这样做。 但即便如此,每次向系统添加租户时,您都会创建一组新的角色,这不是必需的。

我在一个大型的多租户应用程序上工作。 我们得出的结论是,每个租户更容易维护单独的数据库,并让Web应用程序自动切换数据库上下文,而不是尝试使用过于复杂的数据库模式来为不同的租户建模。

好处

  1. 租户数据默认划分为不同的数据库
  2. 租户数据可以导出为客户端MI的数据库转储
  3. 数据库设计大大简化

缺点

  1. 您必须管理多个数据库 – 操作挑战
  2. 您必须开发数据库切换代码

使用多个数据库实现

  1. 我们使用了一个基于帐户代码的客户端设置的配置数据库。 该帐户代码可以来自登录屏幕,也可以将子域映射到客户端代码。
  2. 当应用程序启动时,您将所有租户加载到缓存中(包含连接字符串)
  3. 在每个请求中,您必须确定客户端,然后切换db上下文

我还开发了一个使用单个数据库的多租户应用程序。 您很快就会遇到问题,确保您不会跨租户数据。 每个查询都需要包含一个租户IDfilter。 因此,数据库查询总是较慢,尽管您可以索引所有可以尝试的内容并改进这种情况。

关于成员资格问题,您可以将成员资格架构安装到每个租户数据库中。

什么行不通

理想的替代方法是动态切换ApplicationName ,但是虽然它似乎有用 ,但ApplicationName不是线程安全的 ,因此这不可靠:

由于单个默认成员资格提供程序实例用于HttpApplication对象提供的所有请求,因此可以同时执行多个请求并尝试设置ApplicationName属性值。 ApplicationName属性对于多次写入不是线程安全的,并且更改ApplicationName属性值可能会导致应用程序的多个用户出现意外行为。 我们建议您避免编写允许用户设置ApplicationName属性的代码,除非必须这样做。 可能需要设置ApplicationName属性的应用程序示例是管理多个应用程序的成员资格数据的管理应用程序。 这样的应用程序应该是单用户应用程序而不是Web应用程序。

替代方案:MembershipReboot

多租户在.Net中很难。 使用内置成员资格的开源替代方法是使用由Brock Allen编写的MembershipReboot 。 它具有一些出色的function,包括开箱即用的多租户支持:

  1. 单租户或多租户帐户管理
  2. 灵活的帐户存储设计(关系/ SQL或对象/ NoSql),使用EF和RavenDB的示例
  3. 声明感知用户身份
  4. 支持帐户注册,电子邮件validation,密码重置等
  5. 多次失败登录尝试的帐户锁定(密码猜测)
  6. 电子邮件通知的可扩展模板
  7. 可定制的用户名,密码和电子邮件validation
  8. 帐户活动和更新的通知系统(例如,用于审计)
  9. 帐户与外部身份提供商(企业或社交)的链接
  10. 支持基于证书的认证
  11. 正确的密码存储(通过PBKDF2)
  12. 可配置的迭代
  13. 默认为OWASP迭代建议(例如2012年为64K)
  14. 通过手机短信或客户端证书支持双因素身份validation

最常见的用例是将其集成到ASP.NET或ASP.NET MVC应用程序中,尽管该库也可以通过网络作为服务使用。

替代方案:ServiceStack REST

如果要构建大量使用JavaScript MVC框架(如AngularJS,EmberJS或BackboneJS)的现代Web应用程序,则另一种方法是使用ServiceStack REST服务。 ServiceStack有很多身份validationfunction ,根据我的SS经验,我发现它有一个经过深思熟虑的API模型。

ServiceStack身份验证