WCF OData 服务行级别安全性



问题

我有一个由实体框架和 SQL 支持的 WCF OData 服务,我正在尝试为其实现行级别访问控制。

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

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

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

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

// 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 实体仅支持初始值设定项、成员和导航属性。这意味着我为获取用户 ID 而添加的任何属性或方法都不起作用,因此这些属性或方法都已消失。

将表达式放在实体类中

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

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

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

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

将用户 ID 列添加到所有表

这会使数据库严重反规范化,并且是不可取的。

忘记表格并使用视图

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


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

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

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

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

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

代理可能会对其进行修改,如下所示

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

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

以下是为您提供的几个选项:

  • Microsoft SQL Server 行级别安全性
  • MySQL FGAC
  • 甲骨文副总裁
  • 公理数据访问过滤器 MD

最后,我最终将表达式放在实体类中并使用接口。

接口

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

最新更新