【问题标题】:WCF OData service row level securityWCF OData 服务行级安全性
【发布时间】:2017-03-23 17:30:18
【问题描述】:

问题

我有一个 WCF OData 服务,由实体框架和 SQL 提供支持,我正在尝试为此实施行级访问控制。

考虑以下数据模型,其中用户有订单,而这些订单有商品:

┌───────┐    ┌────────┐    ┌────────┐
│User   │    │Order   │    │Item    │
├───────┤    ├────────┤    ├────────┤
│UserID │    │OrderID │    │ItemID  │
└───────┘    │UserID  │    │OrderID │
             └────────┘    └────────┘

应限制用户只能查看自己的订单以及这些订单的订单商品。

为了实现这个,我使用WCF query interceptors,一个基本的实现是:

// currentUser is request-scoped User entity of the logged in user

[QueryInterceptor("Orders")]
public Expression<Func<Order, bool>> OrderInterceptor()
{
    return order => order.UserID == currentUser.UserID;
}

[QueryInterceptor("Items")]
public Expression<Func<Item, bool>> ItemInterceptor()
{
    return item => item.Order.UserID == currentUser.UserID;
}

但是,我想调用拦截器内部的通用代码,因为实体很多,而且访问控制规则不仅仅是匹配用户 ID。

可能的解决方案

My previous question 处理了从拦截器调用公共方法,以返回多种类型的表达式。提供的答案解决了这个问题,但事实证明这只是冰山一角。

使用界面

public interface ICommonInterface
{
    int GetUserID();
}

public partial class Item : ICommonInterface
{
    public int GetUserID()
    {
        return this.Order.UserID;
    }
}

[QueryInterceptor("Items")]
public Expression<Func<Item, bool>> ItemInterceptor()
{
    return CommonFilter<Item>();
}

private Expression<Func<T, bool>> CommonFilter<T>() where T : class, ICommonInterface
{
    return entity => entity.GetUserID() == currentUser.UserID
}

除了 LINQ 到实体onlysupportsinitializers,members,andnavigationproperties。这意味着我添加的用于获取用户 ID 的任何属性或方法都将不起作用,因此这些已失效。

将表达式放入实体类

与其让每个实体返回其关联的用户 ID,不如让它自己实现过滤器。由于过滤器对类型而不是实例进行操作,因此它必须是静态的。

public partial class Item : ICommonInterface
{
    public static Expression<Func<Item, bool>> CurrentUserFilter(int userID)
    {
        return item => item.Order.UserID == userID;
    }
}

除了interfacesdon't allowstatic methods,所以我们必须用抽象基类替换它。除了抽象类alsodon't allowstatic methods

重点是对多个实体类型应用相同的过滤逻辑,所以如果无法从CommonFilter调用过滤表达式方法,则将其放在实体类中没有多大意义。

将 UserID 列添加到所有表

这严重破坏了数据库的规范化,是不可取的。

忘记表格并使用视图

不使用 Items 表,而是创建一个 Items 视图,在每一行中包含用户 ID。我还没有尝试过,因为这是一个很大的变化。


所以问题是,如何在我的服务中实现记录级别的安全性?

【问题讨论】:

    标签: c# entity-framework wcf linq-to-entities access-control


    【解决方案1】:

    使用数据库的原生数据过滤功能或使用 SQL 代理。

    SQL 代理是位于应用程序和数据库之间的组件。他们拦截 SQL 语句并对其进行修改,以便从数据库中选择相关内容。

    例如,您的应用可能会代表 Alice 发送以下内容:

    SELECT * FROM records WHERE recordDate='2017-01-01'
    

    并且代理可能会修改如下

    SELECT * FROM records WHERE recordDate='2017-01-01' AND owner='Alice'
    

    这称为动态数据过滤和动态数据屏蔽。

    这里有几个选项供您选择:

    【讨论】:

      【解决方案2】:

      最后,我把表达式放到了实体类中,并使用了接口。

      界面

      public interface ICommonInterface<T>
      {
          Expression<Func<T, bool>> CurrentUserFilter(int userID);
      }
      

      实体部分类

      接口中不允许使用静态方法,因此表达式必须是实例方法。

      public partial class Item : ICommonInterface
      {
          public Expression<Func<Item, bool>> CurrentUserFilter(int userID)
          {
              return item => item.Order.UserID == userID;
          }
      }
      

      通用过滤器

      由于过滤器是一个实例方法,我们需要创建一个虚拟实例来调用它(不是最漂亮的)。

      private Expression<Func<T, bool>> DefaultFilter<T>()
          where T : class, ICommonFilter<T>, new()
      {
          Expression<Func<T, bool>> userFilter = new T().UserFilter(currentUser.UserID);
          // Common filtering code...
      
          return userFilter;
      }
      

      查询拦截器

      [QueryInterceptor("Items")]
      public Expression<Func<Item, bool>> InterceptItemRead()
      {
          return DefaultFilter<Item>();
      }
      

      【讨论】:

        猜你喜欢
        • 2012-02-13
        • 1970-01-01
        • 1970-01-01
        • 2010-12-06
        • 1970-01-01
        • 2022-07-14
        • 2012-07-07
        • 2013-09-23
        • 1970-01-01
        相关资源
        最近更新 更多