处理多租户站点中的数据访问

本文关键字:数据 访问 站点 处理 | 更新日期: 2023-09-27 18:28:11

我很感激在基于MVC的多租户站点中提供一些关于数据访问/控制的指针:

有没有更好/更安全/更优雅的方法来确保在多租户网站中,用户只能处理自己的数据。有许多租户使用相同的应用程序:firstTenant.myapp.com,secondTenant.miapp.com…

    //
    // GET: /Customer/
    // show this tenant's customer info only
    public ViewResult Index()
    {
        //get TenantID from on server cache
        int TenantID =  Convert.ToInt16( new AppSettings()["TenantID"]);
        return View(context.Customers.ToList().Where(c => c.TenantID == TenantID));
    }

如果用户是第一次登录,并且该租户/用户没有服务器端缓存,则AppSettings会检查数据库并将TenantID存储在缓存中。

数据库中的每个表都包含字段TenantID,用于将对数据的访问权限限制为仅适用的Tenant。

所以,说到点子上,如果数据属于当前租户,我可以做一些更"高效"的事情吗?

示例:

当firstTenant管理员尝试编辑用户4的一些信息时,url具有:http://firstTenant.myapp.com/User/Edit/4

假设ID为2的用户属于secondTenant。来自firstTenant看跌期权的管理员http://firstTenant.myapp.com/User/Edit/2在url中,并尝试获取不属于他的公司的信息。

为了防止控制器中出现这种情况,我检查正在编辑的信息是否为当前租户所有。

    //
    // GET: /User/Edit/
    public ActionResult Edit(int id)
    {
        //set tennant ID
        int TenanatID = Convert.ToInt32(new AppSettings()["TenantID"]);
        //check if asked info is actually owned by this tennant
        User user = context.Userss.Where(u => u.TenantID == TenantID).SingleOrDefault(u => u.UserID == id);
        //in case this tenant doesn't have this user ID, ie.e returned User == null
        //something is wrong, so handle bad request
        //
        return View(user);
    }

基本上,这种集合需要放置在每个可以访问任何数据的控制器中。有没有(以及如何)更好的方法来处理这个问题?(过滤器、属性…)

处理多租户站点中的数据访问

我选择使用操作过滤器来完成此操作。这可能不是最优雅的解决方案,但它是迄今为止我们尝试过的最干净的解决方案。

我将租户(在我们的情况下,它是一个团队)保存在这样的URL中:https://myapp.com/{team}/tasks/details/1234

我使用自定义绑定将{team}映射到实际的Team对象中,因此我的操作方法如下所示:

[AjaxAuthorize, TeamMember, TeamTask("id")]
public ActionResult Details(Team team, Task id)

TeamMember属性验证当前登录的用户是否实际属于团队。它还验证了团队的真实存在:

public class TeamMemberAttribute : ActionFilterAttribute
{
  public override void OnActionExecuting(ActionExecutingContext filterContext)
  {
    base.OnActionExecuting(filterContext);
    var httpContext = filterContext.RequestContext.HttpContext;
    Team team = filterContext.ActionParameters["team"] as Team;
    long userId = long.Parse(httpContext.User.Identity.Name);
    if (team == null || team.Members.Where(m => m.Id == userId).Count() == 0)
    {
        httpContext.Response.StatusCode = 403;
        ViewResult insufficientPermssions = new ViewResult();
        insufficientPermssions.ViewName = "InsufficientPermissions";
        filterContext.Result = insufficientPermssions;
    }
  }
}

类似地,TeamTask属性确保有问题的任务实际上属于团队。

由于我的应用程序使用子域(sub1.app.com,sub2.app.com…..),我基本上选择:

a) 使用以下代码缓存租户和的信息

b) 如Ragesh&文件:

(以下代码来自上的博客:http://www.developer.com/design/article.php/10925_3801931_2/Introduction-to-Multi-Tenant-Architecture.htm)

// <summary>
// This class is used to manage the Cached AppSettings
// from the Database
// </summary>
public class AppSettings
{
// <summary>
// This indexer is used to retrieve AppSettings from Memory
// </summary>
public string this[string Name]
{
  get
  {
     //See if we have an AppSettings Cache Item
     if (HttpContext.Current.Cache["AppSettings"] == null)
     {
        int? TenantID = 0;
        //Look up the URL and get the Tenant Info
        using (ApplContext dc =
           new ApplContext())
        {
           Site result =
                  dc.Sites
                  .Where(a => a.Host ==
                     HttpContext.Current.Request.Url.
                        Host.ToLower())
                  .FirstOrDefault();
           if (result != null)
           {
              TenantID = result.SiteID;
           }
        }
        AppSettings.LoadAppSettings(TenantID);
     }
     Hashtable ht =
       (Hashtable)HttpContext.Current.Cache["AppSettings"];
     if (ht.ContainsKey(Name))
     {
        return ht[Name].ToString();
     }
     else
     {
        return string.Empty;
     }
  }
}
// <summary>
// This Method is used to load the app settings from the
// database into memory
// </summary>
public static void LoadAppSettings(int? TenantID)
{
  Hashtable ht = new Hashtable();
  //Now Load the AppSettings
  using (ShoelaceContext dc =
     new ShoelaceContext())
  {
      //settings are turned off
      // no specific settings per user needed currently
     //var results = dc.AppSettings.Where(a =>
     //   a.in_Tenant_Id == TenantID);
     //foreach (var appSetting in results)
     //{
     //   ht.Add(appSetting.vc_Name, appSetting.vc_Value);
     //}
                ht.Add("TenantID", TenantID);
  }
  //Add it into Cache (Have the Cache Expire after 1 Hour)
  HttpContext.Current.Cache.Add("AppSettings",
     ht, null,
     System.Web.Caching.Cache.NoAbsoluteExpiration,
     new TimeSpan(1, 0, 0),
     System.Web.Caching.CacheItemPriority.NotRemovable, null);
     }
  }

如果你想在控制器中的每个操作上执行这样的通用代码,你可以这样做:

protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
    base.OnActionExecuting(filterContext);
    // do your magic here, you can check the session and/or call the database
}

我们也使用ASP.NET MVC开发了一个多租户应用程序,在每个查询中包含租户ID是完全可以接受的,也是非常必要的。我不确定你在哪里托管你的应用程序,但如果你可以使用SQL Azure,他们有一个名为Federations的新产品,可以让你轻松管理多租户数据。一个很好的特性是,当您打开连接时,您可以指定租户ID,此后执行的所有查询都只会影响租户数据。它本质上只是在每个请求中包含他们的租户ID,这样你就不必手动执行。(请注意,联合数据并不是一个新概念,微软最近刚刚发布了他们自己的实现)