我很感激有关基于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);
}
基本上,这种类型的设置放在每个可以访问任何数据的控制器中。有(以及如何)更好的方法来处理这个? (过滤器,属性......)
答案 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医生:
// <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,因此您不必手动执行。 (请注意,联合数据不是一个新概念,微软最近刚刚发布了自己的实现)