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'
这称为动态数据过滤和动态数据屏蔽。
这里有几个选项供您选择:
- 微软SQL服务器Row-Level Security
- MySQL FGAC
- Oracle VPD
- Axiomatics Data Access Filter MD
最后,我最终将表达式放入实体 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>();
}
问题
我有一个 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。
可能的解决方案
使用界面
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
将表达式放入实体中 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'
这称为动态数据过滤和动态数据屏蔽。
这里有几个选项供您选择:
- 微软SQL服务器Row-Level Security
- MySQL FGAC
- Oracle VPD
- Axiomatics Data Access Filter MD
最后,我最终将表达式放入实体 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>();
}