Layer-Design:在哪里检查数据库读取/更新的权限?

ala*_*eno 12 database model-view-controller scala web-applications playframework

在大多数情况下,您希望用户只能访问用户自己创建的数据库中的实体.例如,如果存在由User1创建的日历,则只有User1应该能够在数据库中读取,更新或删除此特定日历及其内容.这不是一般的授权 - 在我的项目中,已经有一个基于角色的授权组件,它检查用户是否属于"日历编辑器"角色,但它不检查是否允许特定用户访问具体日历.

因此,最终必须在当前请求的用户标识和表示所请求日历的所有者的用户标识之间进行比较.但我想知道在哪里这样做.我的想法:

  • 我可以在DAO级别上完成.但是,每个DAO方法都需要一个表示user-id的附加参数,这使得这些方法更加冗长并降低了可重用性.

    例如

    def findCalById(id: Int): Future[Option[Calendar]]

    def findCalById(id: Int, ownerId: Int ): Future[Option[Calendar]]

    优点是权限检查基本上在查询级别完成,这意味着如果用户无法访问日历,则不会从数据库返回日历.但话又说回来:如果在某些情况下没有返回日历,那么如何区分不存在的日历和当前用户无法访问的现有日历?两种不同的情景,产生相同的结果.

  • 另一种选择可能是将DAO保持在此范围之外,并在服务层或类似的东西中进行检查.这意味着在DAO返回请求的日历之后执行检查.这种方法听起来比另一种方法更灵活,但它也意味着如果用户无法访问所请求的日历,则所请求的日历仍然消耗带宽和内存,因为在任何一种情况下都从数据库获取.

也许我还没有考虑其他选项.有没有最佳做法?

顺便说一下:我的网络应用程序中没有日历,这只是一个说明问题的例子.

Dim*_*ima 7

我认为,关键是当你说DAO方法"降低可重用性"时要考虑你的意思.如果您强制实施用户访问权限的要求对DAO的所有应用程序都是通用的,那么在DAO级别执行此操作实际上会提高可重用性而不是减少它:使用DAO的每个人都可以从这些检查中受益,而不是必须实现它们他们自己.

您可以使用户标识为隐式参数,以使这些方法对上游用户更友好.你也可以让它返回一个Try(或者,也许是一个Either)来解决你关于区分缺失和不可访问的对象案例的问题:

case class UserId(id: Int)
def findCalById(id: Int)(implicit user: UserId): Future[Try[Option[Calendar]]] = ???
Run Code Online (Sandbox Code Playgroud)

然后,调用者可以这样做:

implicit val currentUser = UserId(request.getUserId)
dao.findCalById(request.getCalendarId).map { 
    case Failure(IllegalAccessException()) => "You are not allowed to use this calendar"
    case Return(None) => "Calendar not found"
    case Return(Some(cal)) => calendarToString(cal)
}
Run Code Online (Sandbox Code Playgroud)

另一方面,如果有可能在没有用户上下文的情况下使用DAO(可能是"管理员"应用程序),那么您可以考虑将其子类化为"常规应用程序"提供访问控制,或者,或许,只需创建一个额外的角色,允许用户访问有关所有权的所有日历,然后在管理应用程序中使用该"超级用户".

我不担心在检查访问权限之前必须加载对象的成本(即使加载对象非常昂贵,也应该是有人试图访问他不拥有的对象的罕见情况).我认为,针对服务层方法的更强有力的论据正是代码的可重用性和模块性:具有公共接口的DAO类的存在表明它至少可能被多个组件重用.考虑到这样的合同是不可执行的,特别是要求所有这些组件都执行自己的访问检查似乎很愚蠢:没有办法确定那些决定在几年后使用DAO类的人,将记住这个要求(或仔细阅读评论).无论如何,如果要生成一个用于访问数据库的层,您也可以使它对某些内容有用.