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; } } |
。
除了接口不允许静态方法,所以我们必须用抽象基类替换它。除了抽象类之外,还不允许使用静态方法。
整点就是对多个实体类型应用相同的过滤逻辑,所以如果不能从
向所有表添加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 |
以及利用查询的解译器
1 2 3 4 5 | [QueryInterceptor("Items")] public Expression<Func<Item, bool>> InterceptItemRead() { return DefaultFilter<Item>(); } |
。