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

时间:2012-03-16 10:14:04

标签: c# asp.net-mvc multi-tenant

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

是否有更好/更安全/更优雅的方式来确保在多租户网站中用户只能处理自己的数据。 有许多租户使用相同的应用程序:firstTenant.myapp.com,secondTenant.myapp.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会在db中进行检查并将TenantID存储在缓存中。

数据库中的每个表都包含TenantID字段,用于限制只对相应租户的数据访问。

那么,为了达到这一点,如果数据属于当前租户,而不是检查每个控制器中的每个操作,我可以做一些更“有效”的事情吗?

示例:

当firstTenant管理员尝试为用户4编辑某些信息时,网址包含: http://firstTenant.myapp.com/User/Edit/4

假设ID为2的用户属于secondTenant。管理员从firstTenant投入 在网址中http://firstTenant.myapp.com/User/Edit/2,并尝试获取不属于其公司的信息。

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

    //
    // 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);
    }

基本上,这种类型的设置放在每个可以访问任何数据的控制器中。有(以及如何)更好的方法来处理这个? (过滤器,属性......)

4 个答案:

答案 0 :(得分:3)

我选择使用动作过滤器来执行此操作。它可能不是最优雅的解决方案,但它是迄今为止我们尝试过的最简洁的解决方案。

我在网址中保留了租户(在我们的情况下,这是一个团队),如下所示: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属性可确保相关任务实际属于团队。

答案 1 :(得分:1)

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

a)使用类似以下代码的内容来缓存有关租户的信息和

b)按照Ragesh& amp; amp; amp; amp; amp; amp; amp; amp; amp; amp; amp; amp; and医生:

(以下代码来自博客: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);

     }
  }

答案 2 :(得分:0)

如果你想在Controller中的每个Action上执行这样的公共代码,你可以这样做:

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

答案 3 :(得分:0)

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