我正在使用ASP.NET MVC3和EF4构建一个简单的多用户(多租户?)应用程序,一个数据库,一个代码库,所有用户都使用相同的URL访问应用程序。一旦用户登录,他们应该只能访问他们的数据,我使用默认的asp.NET成员资格提供程序,并在每个数据表上添加了“UserId”Guid字段。显然,我不希望用户A对用户B的数据有任何访问权限,因此我几乎在控制器上的每个操作中都添加了以下内容。
public ActionResult EditStatus(int id)
{
if (!Request.IsAuthenticated)
return RedirectToAction("Index", "Home");
var status = sService.GetStatusById(id);
// check if the logged in user has access to this status
if (status.UserId != GetUserId())
return RedirectToAction("Index", "Home");
.
.
.
}
private Guid GetUserId()
{
if (Membership.GetUser() != null)
{
MembershipUser member = Membership.GetUser();
Guid id = new Guid(member.ProviderUserKey.ToString());
return id;
}
return Guid.Empty;
}
这种重复肯定是错误的,必须有一种更优雅的方式来确保我的用户无法访问彼此的数据 - 我缺少什么?
答案 0 :(得分:12)
我错过了什么?
自定义模型装订器:
public class StatusModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
// Fetch the id from the RouteData
var id = controllerContext.RouteData.Values["id"] as string;
// TODO: Use constructor injection to pass the service here
var status = sService.GetStatusById(id);
// Compare whether the id passed in the request belongs to
// the currently logged in user
if (status.UserId != GetUserId())
{
throw new HttpException(403, "Forbidden");
}
return status;
}
private Guid GetUserId()
{
if (Membership.GetUser() != null)
{
MembershipUser member = Membership.GetUser();
Guid id = new Guid(member.ProviderUserKey.ToString());
return id;
}
return Guid.Empty;
}
}
然后你会在Application_Start
注册这个模型绑定器:
// Could use constructor injection to pass the repository to the model binder
ModelBinders.Binders.Add(typeof(Status), new StatusModelBinder());
最后
// The authorize attribute ensures that a user is authenticated.
// If you want it to redirect to /Home/Index as in your original
// example if the user is not authenticated you could write a custom
// Authorize attribute and do the job there
[Authorize]
public ActionResult EditStatus(Status status)
{
// if we got that far it means that the user has access to this resource
// TODO: do something with the status and return some view
...
}
结论:我们已经把这个控制器放在控制器应该是这样的饮食方式: - )
答案 1 :(得分:1)
试图了解这个实现(我有完全相同的问题),我发现Scott Hanselman描述的类似方法
http://www.hanselman.com/blog/IPrincipalUserModelBinderInASPNETMVCForEasierTesting.aspx
public class IPrincipalModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
if (bindingContext == null)
{
throw new ArgumentNullException("bindingContext");
}
IPrincipal p = controllerContext.HttpContext.User;
return p;
}
}
void Application_Start()
{
RegisterRoutes(RouteTable.Routes); //unrelated, don't sweat this line.
ModelBinders.Binders[typeof(IPrincipal)] = new IPrincipalModelBinder();
}
[Authorize]
public ActionResult Edit(int id, IPrincipal user)
{
Dinner dinner = dinnerRepository.FindDinner(id);
if (dinner.HostedBy != user.Identity.Name)
return View("InvalidOwner");
var viewModel = new DinnerFormViewModel {
Dinner = dinner,
Countries = new SelectList(PhoneValidator.Countries, dinner.Country)
};
return View(viewModel);
}
对于我自己的总MVC菜鸟,这有点容易理解。