处理多租户站点中的数据访问
本文关键字:数据 访问 站点 处理 | 更新日期: 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,这样你就不必手动执行。(请注意,联合数据并不是一个新概念,微软最近刚刚发布了他们自己的实现)