Spring Data JPA и spring-security: фильтр на уровне базы данных (особенно для пейджинга)

Я пытаюсь добавить безопасность на уровне метода в свой проект с открытым исходным кодом, используя аннотации и spring-security. Проблема, с которой я сейчас сталкиваюсь, - это методы findAll, особенно для пейджинга (например, возврат страницы).

Использование @PostFilter работает со списками (но я лично считаю, что фильтровать в приложении, а не в базе данных — не очень хорошая идея), но совершенно не работает при поисковых запросах.

Это проблематично, потому что у меня есть объект, содержащий List<Compound>. Существуют разные реализации соединения, и пользователь может иметь право только на чтение одного из соединений. Составной использует наследование TABLE_PER_CLASS. Репозитории реализуют QueryDslPredicateExecutor.

Я думаю, что нужно добавить предикат к каждому запросу, который ограничивает возвращаемые результаты в зависимости от текущего пользователя. Однако я немного не понимаю: а) как должна выглядеть модель данных для пользователя и ролей и б) как затем создать предикат (это, вероятно, легко, когда модель определена). Или же querydsl уже предлагает фильтрацию на основе типов (элементов, содержащихся в запрашиваемом классе)?


person beginner_    schedule 27.02.2013    source источник
comment
Для вопроса A см. раздел «Схема пользователя»   -  person Maksym Demidas    schedule 27.02.2013
comment
Я имел в виду, что я могу настроить запросы с учетом ролей, которые имеют текущие пользователи, например. должна быть связь между данной сущностью и ролью и между ролью и пользователем.   -  person beginner_    schedule 27.02.2013
comment
Затем загляните в ACL static.springsource.org/spring-security/site/docs/3.2.x/ и соответствующую схему БД static.springsource.org/spring-security/site/docs/3.2.x/   -  person Maksym Demidas    schedule 27.02.2013


Ответы (2)


В настоящее время такой поддержки нет, но она есть в дорожной карте. Вы можете следить за общим прогрессом в DATACMNS-293.

person Oliver Drotbohm    schedule 28.02.2013

На данный момент придумал следующее решение. Поскольку мой проект довольно прост, это может не сработать для более сложного проекта.

  1. пользователь может либо прочитать все, либо ничего из сущностей определенного класса

следовательно, любой метод запроса может быть аннотирован с помощью @PreAuthorize, содержащего hasRole.

Исключением является объект Container в моем проекте. Он может содержать любой подкласс Compound, и пользователь может не иметь права просматривать их все. Они должны быть фильтрующими.

Для этого я создал сущности User и Role. Compound имеет отношение OneToOne к Role, и эта роль является "read_role" для этого Compound. User и Role имеют отношение ManyToMany.

@Entity
public abstract class Compound {    
    //...
    @OneToOne    
    private Role readRole;
    //...   
}

Все мои репозитории реализуют QueryDSLPredicateExecutor, и здесь это очень удобно. Вместо создания пользовательских методов findBy в репозитории мы создаем их только на сервисном уровне и используем repositry.findAll(predicate) и repository.findOne(predicate). Предикат содержит фактический пользовательский ввод + «фильтр безопасности».

@PreAuthorize("hasRole('read_Container'")
public T getById(Long id) {        
    Predicate predicate = QCompoundContainer.compoundContainer.id.eq(id);
    predicate = addSecurityFilter(predicate);
    T container = getRepository().findOne(predicate);        
    return container;
}

private Predicate addSecurityFilter(Predicate predicate){        
    String userName = SecurityContextHolder.getContext().getAuthentication().getName();            
    predicate = QCompoundContainer.compoundContainer.compound.readRole
        .users.any().username.eq(userName).and(predicate);        
    return predicate;
}

Примечание. QCompoundContainer — это класс «метамодели», созданный QueryDSL.

Наконец, вам, вероятно, нужно инициализировать путь QueryDSL с Container на User:

@Entity
public abstract class CompoundContainer<T extends Compound> 
    //...
    @QueryInit("readRole.users") // INITIALIZE QUERY PATH
    @ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL,
            targetEntity=Compound.class)
    private T compound;
    //...
}

Пропуск этого последнего шага может привести к ошибке NullPointerException.

Дополнительная подсказка: CompoundService автоматически устанавливает роль при сохранении:

if (compound.getReadRole() == null) {
    Role role = roleRepository.findByRoleName("read_" + getCompoundClassSimpleName());
    if (role == null) {
        role = new Role("read_" + getCompoundClassSimpleName());
        role = roleRepository.save(role);
    }
    compound.setReadRole(role);
}
compound = getRepository().save(compound)

Это работает. Минус немного очевиден. Один и тот же Role связан с каждым экземпляром одной и той же реализации класса Compound.

person beginner_    schedule 28.02.2013