java spring安全@postfilter性能

zengzsys  于 2021-09-29  发布在  Java
关注(0)|答案(1)|浏览(319)

我的java spring应用程序使用ACL,它有一个服务方法来检索与给定用户对应的所有对象:

@Override
    @PostFilter("hasPermission(filterObject, 'READ') or hasPermission(filterObject, 'ADMINISTRATION')")
    public List<SomeClass> findAll() {
        return SomeClassRepository.findAll();
    }

不幸的是,当数据库中有许多对象时,此方法需要花费太多时间才能完成(超过1秒)。可能是因为它将首先从数据库中获取所有对象,然后在内存中逐个过滤它们。如何在不失去spring ACL的好处的情况下对此进行优化?
编辑:我现在提出的解决方案是为 acl_sidacl_entry 并通过这些存储库获取感兴趣对象的ID。与上述方法相比,这使我的执行时间提高了10倍。新代码如下所示:

@Override
    @PostFilter("hasPermission(filterObject, 'READ') or hasPermission(filterObject, 'ADMINISTRATION')")
    public List<SomeClass> findAll() {
        List<SomeClass> result = new ArrayList<SomeClass>();
        Long userId = (Long) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        AclSid sid = aclSidRepository.findBySid(Long.toString(userId));
        List<AclEntry> aclEntries = aclEntryRepository.findBySid(sid);
        for (AclEntry aclEntry : aclEntries) {
            AclObjectIdentity aclObjectIdentity = aclEntry.getAclObjectIdentity();
            AclClass aclClass = aclObjectIdentity.getObjectIdClass();
            if (aclClass.getClassName().equals("com.company.app.entity.SomeClass")) {
                Optional<SomeClass> SomeClass = SomeClassRepository
                        .findById(aclObjectIdentity.getObjectIdIdentity());
                if (SomeClass.isPresent()) {
                    result.add(SomeClass.get());
                }
            }
        }
        return result;
    }
jmo0nnb3

jmo0nnb31#

由于spring过滤内存中的信息,性能将取决于结果的实际数量:如果有大量结果,恐怕在过滤信息之前只缓存存储库结果可能是一个合适的解决方案。
为了解决这个问题,您可以在数据库级别过滤结果。我想到了两种方法:
任用 Specification s、 并根据spring security公开的主体信息在数据库级别过滤结果 SecurityContext 包括必要的过滤器 Predicates 以限制返回的信息。
或者,如果您使用的是hibernate,请使用实体过滤器,根据spring security公开的主体信息,再次应用必要的数据限制。请参阅此相关so问题,该问题提供了有关解决方案的详细信息。
请考虑,例如,Spring Data 的使用情况 Specification S
而不是 SomeClass ,让我们假设我们正在处理银行账户。让我们创建相应的实体:

@Entity
public class BankAccount {

  @Id
  private String accountNumber;
  private Float balance;
  private String owner;
  private String accountingDepartment;

  //...

}

以及相应的存储库:

public interface BankAccountRepository extends Repository<BankAccount, String>, JpaSpecificationExecutor<BankAccount, String> {
}

为了根据执行查询的用户筛选信息,我们可以定义一个实用工具方法,该方法基于用户权限返回 List 属于 Predicates 我们可以在以后添加到我们在某个特定应用程序中使用的 Specification 过滤银行账户时:

public static List<Predicate> getPredicatesForRestrictingDataByUser(Root<BankAccount> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
  // I realized in your edit that you are returning the user id instead of the user object.
  // There is nothing wrong with it but you are losing a valuable information: if you provide
  // a convenient UserDetails implementation you can have direct access to the authorities a user has, etc
  User user = SecurityContextHolder.getContext().getAuthentication().getPrincipal();

  // Restrict data based on actual permissions

  // If the user is an admin, we assume that he/she can see everything, and we will no return any predicates
  if (hasAuthority(user, 'ADMINISTRATION')) {
    return Collections.emptyList();
  }

  // Let's introduce the accounting manager role.
  // Suppose that an accounting manager can see all the accounts in his/her department
  if (hasAuthority(user, 'ACCOUNTING_MANAGER')) {
    return Collections.singletonList(cb.equal(root.get(BankAccount_.accountingDeparment), user.getDepartment()))
  }

  // In any other case, a user can only see the bank account if he/she is the account owner
  return Collections.singletonList(cb.equal(root.get(BankAccount_.owner), user.getId()));
}

哪里 hasAuthority 可能看起来像:

public static boolean hasAuthority(User user, String... authorities) {
  if (user instanceof UserDetails) {
    for (String authority : authorities) {
      return authentication.getAuthorities().stream()
        .map(GrantedAuthority::getAuthority)
        .findAny(a -> a.equals(authority))
        .isPresent();
    }
  }

  return false;
}

现在,在构建应用程序时使用这些方法 Specification S例如,考虑:

public static Specification<BankAccount> getBankAccounts(final BankAccountFilter filterCriteria) {
  return new Specification<BankAccount>() {

    @Override
    public Predicate toPredicate(Root<BankAccount> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
      List<Predicate> predicates = new ArrayList<Predicate>();

      // Build your predicate list according to the user provided filter criteria
      String accountNumber = filterCriteria.getAccountNumber();
      if (accountNumber != null) {
        predicates.add(cb.equal(root.get(BankAccount_.accountNmber), accountNumber);
      }

      //...

      // And now, restrict the information a user can see
      // Ideally, define getPredicatesForRestrictingDataByUser in a generic class more suitable for being reused
      List<Predicate> predicatesForRestrictingDataByUser = getPredicatesForRestrictingDataByUser(root, query, cb);
      predicates.addAll(predicatesForRestrictingDataByUser);

      Predicate predicate = cb.and(predicates.toArray(new Predicate[predicates.size()]));
      return predicate;
    }
  };
}

请原谅我的简单用例,但我希望你能理解。
@osamaabdulrehman在他的评论中提出的解决方案看起来也很有趣,尽管老实说我从未测试过。

相关问题