领域驱动设计中的访问控制

我读过 DDD 和访问控制,我发现以下两种观点之间有些矛盾:

  • “安全问题应在域外处理”
  • “访问控制要求是特定于域的”

我正在寻找这方面的最佳实践。因此,我应该把领域驱动设计的访问控制逻辑放在哪里,我应该如何实现它?

(通过 DDD + CQRS + ES 更具体一些。)

我认为它应该接近业务逻辑,例如一个用户故事可以是这样的:

用户可以通过发送用户名、业余爱好列表、简历等来编辑个人资料。

基于用户故事,我们实现域模型和服务,例如:

UserService
editProfile(EditUserProfileCommand command)
User user = userRepository.getOneById(command.id)
user.changeName(command.name)
user.changeHobbies(command.hobbies)
user.changeCV(command.cv)


UserRepository
User getOneById(id)


User
changeName(String name)
changeHobbies(String[] hobbies)
changeCV(String cv)

这是好的,但是 HIS profile的故事部分在哪里呢?

这显然是基于属性的访问控制,因为我们应该编写这样的规则:

deny all, but if subject.id = resource.owner.id then grant access

但是我们应该在哪里执行这个规则,我们应该如何执行它?

19808 次浏览

那么我应该把访问控制逻辑放在哪里呢?

根据这一点: https://softwareengineering.stackexchange.com/a/71883/65755的策略执行点应该正好在 UserService.editProfile()的调用之前。

我得出了同样的结论: 它不可能出现在 UI 中,因为多个 UI 会导致代码重复。它应该是在域事件创建之前,因为它们表明我们已经在系统中做了一些事情。因此,我们可以限制对域对象或使用这些域对象的服务的访问。通过 CQRS,我们不需要通过读模型拥有域对象,只需要服务,所以如果我们想要一个通用的解决方案,我们必须限制对服务的访问。我们可以将访问决策放在每个服务操作的开头,但那将是 grant all, deny x安全反模式。

我应该如何实施它?

这取决于哪种访问控制模型适合该域,因此它取决于用户情景。通过访问决策,我们通常发送一个访问请求,并等待一个权限返回。访问请求通常包括以下几个部分: 主题、资源、操作、环境。因此,主体需要对环境中的资源执行操作的权限。首先,我们识别主题,然后对其进行身份验证,然后是授权,在授权之后,我们检查访问请求是否符合我们的访问策略。每个访问控制模型都以类似的方式工作。办公室。他们可能缺少这些步骤中的一些,但这并不重要..。

我创建了一个访问控制模型的简短列表。我将规则、策略放入注释中,但是通常我们应该将它们存储在一个数据库中,如果我们想要有一个良好的可维护的系统,可能是 XACML 格式的..。

  • 通过基于身份的访问控制(IBAC) ,我们有一个身份权限存储(访问控制列表,功能列表,存取控制矩阵)。例如,通过访问控制列表,我们存储可以拥有权限的用户或组的列表。

    UserService
    @AccessControlList[inf3rno]
    editProfile(EditUserProfileCommand command)
    
  • By lattice based access control (LBAC) the subject has a clearance level, the resource has a required clearance level, and we check which level is higher...

    @posseses[level=5]
    inf3rno
    
    
    UserService
    @requires(level>=3)
    editProfile(EditUserProfileCommand command)
    
  • By role based access control (RBAC) we define subject roles and we grant permissions to subjects whose act the actual role.

    @roles[admin]
    inf3rno
    
    
    UserService
    @requires(role=admin)
    editProfile(EditUserProfileCommand command)
    
  • By attribute based access control (ABAC) we define subject, resource and environment attributes and we write our policies based on them.

    @attributes[roles=[admin]]
    inf3rno
    
    
    UserService
    @policy(subject.role=admin or resource.owner.id = subject.id)
    editProfile(EditUserProfileCommand command)
    @attribute(owner)
    Subject getOwner(EditUserProfileCommand command)
    
  • By policy based access control (PBAC) we don't assign our policies to anything else, they are standalone.

    @attributes[roles=[admin]]
    inf3rno
    
    
    UserService
    editProfile(EditUserProfileCommand command)
    deleteProfile(DeleteUserProfileCommand command)
    @attribute(owner)
    Subject getOwner(EditUserProfileCommand command)
    
    
    @permission(UserService.editProfile, UserService.deleteProfile)
    @criteria(subject.role=admin or resource.owner.id = subject.id)
    WriteUserServicePolicy
    
  • By risk-adaptive access control (RAdAC) we base our decision on the relative risk profile of the subject and the risk level of the operation. This cannot be described with rules I think. I am unsure of the implementation, maybe this is what stackoverflow uses by its point system.

  • By authorization based access control (ZBAC) we don't do identification and authentication, instead we assign permissions to identification factors. For example if somebody sends a token, then she can have access to a service. Everything else is similar to the previous solutions. For example with ABAC:

    @attributes[roles=[editor]]
    token:2683fraicfv8a2zuisbkcaac
    
    
    ArticleService
    @policy(subject.role=editor)
    editArticle(EditArticleCommand command)
    

    因此,所有知道 2683fraicfv8a2zuisbkcaac令牌的人都可以使用该服务。

诸如此类。

还有许多其他的型号,最合适的总是取决于你的客户的需要。

总结一下

- "security concerns should be handled outside the domain"
- "access control requirements are domain specific"

两者都可能是正确的,因为安全性不是域模型的一部分,但是它的实现依赖于域模型和应用程序逻辑。

两年后编辑 2016-09-05

自从我以 DDD 新手的身份回答了自己的问题后,我读了 Vaughn Vernon 的 执行领域驱动设计。这是一本关于这个主题的有趣的书。下面是其中的一段话:

这就构成了一个新的有限上下文——身份与访问 上下文-并将通过标准被其他有界上下文使用 DDD 集成技术 访问上下文是一个通用子域。产品将被命名为 爱情。

因此,根据弗农可能是最好的解决方案,移动访问控制到一个通用的子域。

但是我们应该在哪里执行这个规则,我们应该如何执行它?

考虑用例,用户名只能由管理员更改,因此授权应该传递给每个实体和域服务行为/方法(包括工厂方法)。

在下面的例子中,Application service 是一个反映应用程序/API 用例的服务。它依赖于域的存储库和授权服务。将所有必需的依赖项注入到应用程序服务中。

根据你的例子,我们可以修改如下:

UserRepository interface
User getOneById(id)


AuthorizationService interface
ActiveUser getActiveUser(String token)


ActiveUser interface
Role getRole()
Id getId()


ApplicationService


//dependencies
UserRepository userRepository
AuthorizationService authorizationService
...


editProfile(RequestContext context, EditUserProfileCommand command)
activeUser = authorizationService.getActiveUser(context.getAccessToken)
User user = userRepository.getOneById(command.id)
user.changeName(activeUser, command.name)
user.changeHobbies(activeUser, command.hobbies)
user.changeCV(activeUser, command.cv)
userRepository.save(user)


===
User
changeName(ActiveUser activeUser, String name)
changeHobbies(ActiveUser activeUser, String[] hobbies)
changeCV(ActiveUser activeUser, String cv)


//example how to handle in entity behavior
changeName(ActiveUser activeUser, String newName)
if activeUser.getRole() != UserRole.ADMINISTRATOR
throw UnauthorizedException()
this.name = newName