关于c#:WCF OData服务行级安全性

WCF OData service row level security

问题

我有一个由实体框架和SQL支持的wcf odata服务,我正试图为它实现行级访问控制。

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

1
2
3
4
5
6
┌───────┐    ┌────────┐    ┌────────┐
│User   │    │Order   │    │Item    │
├───────┤    ├────────┤    ├────────┤
│UserID │    │OrderID │    │ItemID  │
└───────┘    │UserID  │    │OrderID │
             └────────┘    └────────┘

用户只能查看自己的订单以及这些订单的订单项。

为了实现这一点,我使用的是WCF查询拦截器,其基本实现是:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 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。

可能的解决方案

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

使用接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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实体之外,只支持初始值设定项、成员和导航属性。这意味着我为获取用户ID而添加的任何属性或方法都不起作用,所以这些都不起作用。

将表达式放入实体类中

不要让每个实体返回其关联的用户ID,而是让它实现过滤器本身。由于过滤器是对类型(而不是实例)进行操作的,所以它必须是静态的。

1
2
3
4
5
6
7
public partial class Item : ICommonInterface
{
    public static Expression<Func<Item, bool>> CurrentUserFilter(int userID)
    {
        return item => item.Order.UserID == userID;
    }
}

除了接口不允许静态方法,所以我们必须用抽象基类替换它。除了抽象类之外,还不允许使用静态方法。

整点就是对多个实体类型应用相同的过滤逻辑,所以如果不能从CommonFilter调用filter expression方法,那么把它放到实体类中的意义就不大了。

向所有表添加userid列

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

忽略表并使用视图

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

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


使用数据库的本机数据筛选功能或使用SQL代理。

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

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

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

代理可以修改如下

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

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

以下是几个选项:

  • Microsoft SQL Server行级安全性
  • mysql fgac
  • Oracle虚拟专用数据库
  • 公理化数据访问过滤器MD

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

接口

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

实体分部类

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

1
2
3
4
5
6
7
public partial class Item : ICommonInterface
{
    public Expression<Func<Item, bool>> CurrentUserFilter(int userID)
    {
        return item => item.Order.UserID == userID;
    }
}

通用筛选器

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

1
2
3
4
5
6
7
8
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;
}

以及利用查询的解译器

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