ASP.MVC用于控制对实体的访问的最佳安全实践

时间:2012-11-08 09:09:05

标签: asp.net-mvc entity-framework security architecture

让我们拥有以下实体的系统:

public class Doctor
{
    public int ID { get; set; }
    public int DepartmentID { get; set; }
    public string Name { get; set; }
    public ICollection<Recipe> Recipes { get; set; }
}

public class Patient
{
    public int ID { get; set; }
    public string Name { get; set; }
    public ICollection<Recipe> Recipes { get; set; }
}

public class Recipe
{
    public int ID { get; set; }
    public int DoctorID { get; set; }
    public int PatientID { get; set; }
    public Doctor Doctor { get; set; }
    public Patient Patient { get; set; }
    public ICollection<RecipeDetails> Details { get; set; }
}

public class RecipeDetails
{
    public int ID { get; set; }
    public Guid SomeGuid { get; set; }
    public double SomeValue { get; set; }
}

我们也有要求:

  • 医生应该能够编辑他的食谱
  • 医生应该只能看到他所在部门的医生的食谱
  • 医生应该能够通过可用的食谱进行搜索
  • Doctor应该能够根据可用的食谱详细信息生成报告

目前我已实施以下安全检查:

public void ValidateAccess(Doctor doctor, Recipe aRecipe, EntityAction action)
{
    if (action == EntityAction.Modify && doctor.ID == aRecipe.Doctor.ID)
        return;
    if (action == EntityAction.Read && doctor.DepartmentID == aRecipe.Doctor.DepartmentID)
        return
    throw new SecurityException();
}

当我有recipe实体时,这对于简单方法非常有用,我可以通过在逻辑方法开始时调用此方法来轻松验证访问。

但是现在我遇到了问题,当我没有确切的实体但有一些统计数据时,这个解决方案不适用于搜索和报告。

让我们想象一下,我想为姓名为“aName”且患有组件“someGuid”的患者生成报告,我将对2个标准进行一些查询:

var res = RecipeRepository.Query(r => aName.Contains(r.Patient.Name)).SelectMany(r => r.Details).Where(d => d.SomeGuid == someGuid).Sum(d => d.SomeValue);

此查询不正确,它将显示所有食谱的统计信息,包括应隐藏的食谱。 要解决这个问题,我们应该在查询中添加访问条件:

currentDoctor.DepartmentID == r.Doctor.DepartmentID

所以现在我有查询:

var res = RecipeRepository.Query(r => aName.Contains(r.Patient.Name) && currentDoctor.DepartmentID == r.Doctor.DepartmentID).SelectMany(r => r.Details).Where(d => d.SomeGuid == someGuid).Sum(d => d.SomeValue);

问题在于我应该将此部分添加到系统中的每个查询中,这些查询对recipes进行任何计算。

更新(2012-11-12):

第一个例子很简单,可以像StuartLC在帖子中提到的那样解决。 但是我们的系统中有更复杂的报告。例如 - 显示所有患者,他们的食谱中包含someGuid。 现在我们的查询从另一个存储库开始,因此我们不能从RecipeRepository应用私有或受保护的方法。 以下是示例查询:

var res = PatientRepository.Query(p => p.Name.Contains(aName) && p.Recipes.Any(r => r.Details.Any(d => d.SomeGuid == someGuid)));

在这种情况下,我们仍然需要将我们的过滤器直接添加到查询中:

var res = PatientRepository.Query(p => p.Name.Contains(aName) && p.Recipes.Any(r => currentDoctor.DepartmentID == r.Doctor.DepartmentID && r.Details.Any(d => d.SomeGuid == someGuid)));

END UPDATE。

可以应用哪种模式或实践来简化此解决方案并防止复制粘贴表达式到每个查询? 我将非常感谢您的回答和建议。

1 个答案:

答案 0 :(得分:1)

如果您的存储库模式Query()方法返回非实现的IQueryable<T>,那么您可以将数据访问限制的关注重构为帮助方法,每个'限制'实体一个,例如:< / p>

private IQueryable<Recipe> ApplyAccessFilters(IQueryable<Recipe> query, User user)
{
    IQueryable<Recipe> filteredQuery = query;

    // Custom method to determine if user is restricted to just his / her recipes
    if (!CheckUserPermission(currentUser, Access.MaySeeAllRecipies) ))
    {
        filteredQuery = filteredQuery
                              .Where(r => r.DepartmentId = currentUser.DepartmentId)
    } // Else no restriction, e.g. Admin Users can access all recipes

    // Other access related filters here

    return filteredQuery;
}

然后,每个需要访问限制的MVC控制器操作都可以使用此方法构建结果过滤器Expression,例如:

var recipes =  RecipeRepository.Query(r => r.SomeFields == someFilters); // NB, do NOT materialize the lambda
var recipesForDoctor =  ApplyAccessFilters(recipes, currentUser) // Access Filter
...
return View(recipesForDoctor); // [AsEnumerable()] - Consider materializing here

您可以用同样的方式处理其他问题,例如分页。

更好的是,您可以设置此访问过滤器fluent,在这种情况下,过滤器很容易看到:

return View(RecipeRepository
            .Query(r => r.SomeFields == someFilters)
            .ApplyAccessFilters(currentUser)
            .Paginate(pagingInfo)
            .AsEnumerable());
相关问题