WCF OData 服务行级安全性

WCF OData service row level security

问题

我有一个 WCF OData 服务,由 Entity Framework 和 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。

可能的解决方案

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

使用界面

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 to Entities only supports initializers, members, and navigation 。这意味着我添加的用于获取用户 ID 的任何属性或方法都不起作用,因此这些都已失效。

将表达式放入实体中 Class

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

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

除了interfaces don't allow static methods, so we'll have to replace it with an abstract base class. Except abstract classes also don't allow static methods

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

将 UserID 列添加到所有表

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

忘掉表,使用视图

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


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

使用数据库的本机数据过滤功能或使用 SQL 代理。

SQL 代理是位于您的应用程序和数据库之间的组件。他们截取了SQL语句并进行了修改,使得相关内容从数据库中选取。

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

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

代理可能会修改如下

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

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

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

最后,我最终将表达式放入实体 class 并使用接口。

界面

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

实体部分 Class

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

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>();
}