NHibernate:更改授权查询

3
我们正在完全重写一个现有产品(并在此过程中从PHP更改为C#)。我们现有产品遇到的问题之一是授权:除非您始终检查并确保人们无法看到他们不被允许看到的内容,否则他们会看到这些内容。最好的地方来确保始终进行检查是哪里?我认为数据层是做这件事的好地方,但欢迎其他建议。
我们使用NHibernate,因此我们构建了一个提供实体的类。它看起来像这样:
class DataProvider {
    TEntity Get<TEntity>(int id) {
        return GetSession().Get<TEntity>(id);
    }

    IQueryable<TEntity> Query<TEntity>()
    {
        return GetSession().Query<TEntity>();
    }

    // ...
}

这使我能够调用DataProvider.Get<Person>(1)或使用from person in DataProvider.Query<Person>() select person进行LINQ查询。使用拦截器将允许我拒绝用户未获得授权的任何单个对象(尽管可以争辩这段代码可以插入到DataProvider.Get中)。当我尝试使用LINQ时,这变得麻烦。假设我有一个包含100万个实体的表,但我只能访问其中的5个实体。使用拦截器会检索整个表,并测试返回的每个实体,但我只需要其中的5个。我想到的最好的方法是注入一些自定义SQL以告诉NHibernate应该检索哪些对象并忽略所有其他对象。我们已经有了一个系统来确定我们是否有权访问对象,让我们假设我们也知道如何从中制作一个查询。

我尝试了什么?

  • 拦截器(太晚了,即使只需要5个对象,也会检索1百万个对象)。
  • 事件(IPostLoadEvent最接近我所需的内容,但它太晚了)。
  • 自定义NhQueryable和DefaultQueryProvider。这会抛出异常:“The constant for 'MyQueryable<Person>' is not supported”,我认为这是由于NHibernate深处的某个地方进行了强制转换。我怀疑NHibernate没有设计成允许自定义查询提供程序或可查询。
  • OnPrepareStatement拦截器。这非常丑陋。它允许我编辑SQL,但我得到的只是一个字符串。这能够工作,但我希望有一种更优雅的方法来做同样的事情。

其他人如何处理授权代码?您如何确保没有人忘记调用授权检查?


这是关于数据或视图方面的授权吗? - kalki
数据。假设我在数据库中有两个人; A人和B人。给定的用户只能访问A人,对于该用户,我希望完全隐藏B人的存在。 - Sander
我会编辑问题,以更恰当地反映主题,即查询封装而不是授权,因为这会误导读者。 - kalki
1
我包含了关于授权的问题,因为也许有更好的方法来解决这个问题。查询封装是我能想到的唯一(自动化)方法,但也许有人可以想出不需要查询封装的方法? - Sander
问题的意图是根据特定的标准获取数据,即查询封装/或任何其他您希望使用的术语,并且与授权无关,这将有助于您自己和其他发帖人。干杯 - kalki
2个回答

1

我认为你应该看一下NHibernate过滤器。

Ayende在这里有一个不错的介绍:http://ayende.com/blog/3993/nhibernate-filters

几年前,我们成功地使用了NH过滤器来处理类似于你的场景。我在下面附上了过滤器的要点。在我们的情况下,所有我们想要控制访问权限的类都继承自一个基类“SecuredObject”。我们还有一个“文件夹”中的“文档”的概念。这里可能有些脱离上下文,但也许可以给你一些想法。

<filter name="GrantedSecuredObjectsOnly" condition=" ... Id IN (SELECT SecurityGrant.SecuredObjectId FROM SecurityGrant WHERE SecurityGrant.SecuredObjectConsumerId=:userId)) OR (Id IN (SELECT Document.Id FROM Document WHERE Document.FolderId IN (SELECT SecurityGrant.SecuredObjectId FROM SecurityGrant WHERE SecurityGrant.SecuredObjectConsumerId=:userId)) ... "/>

userId当然设置为当前登录用户的ID。


谢谢,这已经接近我们所需的了。但是每次从数据库检索对象仍需要调用自定义函数。我们正在测试一种解决方案,即每个查询都会自动进行身份验证(我们已经封装了对Session.Query<>()的调用,并调用1个函数来验证授权)。然后,对于NHibernate实例化的每个对象,我们再次检查授权。到目前为止,这种方法似乎是有效的。 - Sander
实际上,在查询过程中,您不需要调用任何自定义内容。当创建NH会话时,可以在“引擎盖下”启用过滤器。应用程序的其余部分实际上并不需要关心它。不过有一个问题 - 当使用NH API的“Session.Get”和“Session.Load”方法时,过滤器不会被应用。无论如何...如果您已经找到了适合您情况的好解决方案 - 那太棒了。 - gurra777

0

编辑:我当时在想什么!

我假设您正在使用身份验证ID / 输入者ID对数据进行标记。

var userId = ...; //your authentication id

from entity in context.Entities    
where context.Entities.Where(x => x.EnteredById == userId && x.Id == entity.Id)
select entity;

我认为这应该可以做到


var query = new AuthorisedDataQuery<Entity>();
var entities = _dataProvider.Get(query);

编写授权数据查询实现应该相当容易。
一个带有规范的查询:伪代码
var userId = ... //authentication
var specification = PredicateBuilder.True<Entity>();
specification = PredicateBuilder.And(specification, x => x.EntityId == userId); //default: build from this
var additionalCriterias = ...;
specification = additionalCriterias == null || additionalCriterias.Length == 0 
                  ? specification 
                  : GetComposedSpecification(additionalCriterias);

var query = from entity in context.Entities
            join anyOtherEntity in context.AnyOtherEntity on entity.EntityId equals anyOtherEntity.Entity.EntityId
            where db.Context.Entities.Where(specification).Contains(entity.EntityId)
            select entity;

简单来说,您正在尝试从默认值中构建查询条件 / 规范

通过LinkKit的谓词构建器 @ http://www.albahari.com/nutshell/predicatebuilder.aspx


这基本上就是我想要实现的。除了在我的代码的各个地方显式调用那段代码,我希望有一个单一的地方来注入那段代码。例如:“从DataProvider.Query<Person>()中选择实体”,应该只返回我允许查看的实体。整个where子句应以某种通用方式添加。 - Sander
封装查询,因此您可以说 _dataProvider.GetAuthorisedData<TEntity>(query)。请查看Ayende的帖子http://ayende.com/blog/3955/repository-is-the-new-singleton,您应该能够了解基本思路。 - kalki
您可以根据需要添加进一步的规格说明。 - kalki
我不确定你的链接如何帮助我。是的,它会创建一个经过筛选的IQueryable(或IEnumerable),但如果我将其与任何东西连接,它不会简单地忽略那些连接实体的检查吗?也许我正在错误的层次上解决这个问题?我会尝试一下看看能否解决它。 - Sander
如果您在每个记录上盖章认证ID /输入者ID,为什么会影响查询?您所做的只是添加关于实体的附加规范,即规范可以是{实体ID、订单日期,...}。 - kalki

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接