如何处理 Spring Boot 应用程序中的多租户问题?

How to handle the multitenancy problem in Spring Boot applications?

假设我有一个具有以下要求的 SpringBoot 应用程序。

  1. 它有一个Userclass(实体)
  2. 每个用户有零个或多个Workspaces(一对多实体关系)
  3. 每个Workspace有零个或多个WorkItems(一对多实体关系)

有一个 CRUD REST API 控制器来管理所有实体,即我们有

  1. UserController -> User 实体的 CRUD 操作
  2. WorkspaceController -> Workspace 实体的 CRUD 操作
  3. WorkItemContoller -> WorkItem 实体的 CRUD 操作

现在有要求...

  1. 一个User只能create/edit/delete他自己的Workspace个实体
  2. 一个User在他自己的Workspaces
  3. 中只能create/edit/deleteWorkItem个实体

此外,假设 User 实体与 SpringSecurity 集成并且我们知道控制器和服务中的当前用户。

那么问题是...

最elegant/clean/maintainable实现用户权限检查的方法是什么?我们如何编写代码,在 Service classes 中将检查用户是否有权对给定资源执行操作。

我现在的做法是有一个像这样的 class,它会在每个 Service 调用中检查权限。

class PermissionManager {
   void checkUserAllowedToUseWorkspace(User u, Workspace w);
   void checkUserAlloweToUseWorkitem(User u, WorkItem)
}

如您所见...随着作用域资源数量的增加...此 class 将变得非常臃肿且难以维护。

有没有人知道以干净且可维护的方式执行此范围资源访问的更好方法?

工作区实体的一些数据库查询:

// DELETE
@Transactional
void deleteByIdAndUserId(String workspaceId, String userId);

userRepository.deleteByIdAndUserId(workspaceId, userId);


// UPDATE
Optional<Workspace> workspace = workspace.findByIdAndUserId(String workspaceId, String userId);

Workspace workspace = workspace.orElseThrow(() -> new RuntimeException("You don't have an workspace under user "))

workspace.setName("MyWorkspace2");

//CREATE
User user = userRepository.findById(SecurityContextHolder...getPriciple().getId())
              .orElseThrow(() -> new RuntimeException("User can not be found by given id"));
workspaceRepository.save(WorkspaceBuilder.builder().user(user).name("firstWorkspace").build());

最干净和可维护的解决方案是利用 Spring 安全性 AOP 来完成任务。

您可以使用 @PreAuthorize 注释,与您的 PermissionManager 服务配对,利用 [=35 的力量允许或拒绝 Controller 级别的访问=] 表达语言.

定义了一个 Service 构造型来检查用户对特定资源(您的示例中的工作区)的访问权限:

@Service
public class PermissionManagerImpl implements PermissionManager {

    @Autowired
    private UserRepository userRepository;

   /**
   * @param authentication the current authenticated user following your authentication scheme
   * @param workspaceId the workspace (or other resource) identifier
   */
    @Override
    public boolean checkUserAllowedToUseWorkspace(Authentication authentication, Long workspaceId) {
        return authentication != null
                /* check that the `authentication` has access to the argument workspace: e.g. userRepository.findWorkspaceByUserNameAndWorkspaceId(authentication.getName(), workspaceId) != null */;
    }

}

您可以在 Controller 方法上定义基于表达式的控制策略,如下所示:

@RestController
public class WorkspaceController {

    // the DIed `permissionManagerImpl` service will be called prior to your endpoint invocation with the current `authentication` and workspace `id` injected
    @PreAuthorize("@permissionManagerImpl.checkUserAllowedToUseWorkspace(authentication, #id)")
    @RequestMapping("/workspaces/{id}")
    public List<Workspace> getWorkspaces(@PathVariable Long id) {
        // retrieve the user workspaces once authorized
    }
}

这将导致可读和可重用的解决方案作用于顶级资源,Controller

您可以在 Expression-based Access Control in the official Spring docs 上了解更多信息。