Как реализовать Spring Security User / Authorities с помощью Hibernate / JPA2? - PullRequest
7 голосов
/ 13 сентября 2010

Я пытаюсь внедрить DAO для работы с аутентификацией базы данных Spring Security в Hibernate / JPA2. Spring использует следующие отношения и ассоциации для представления пользователя и ролей:

alt text

представлен как запрос на создание postgresql:

CREATE TABLE users
(
  username character varying(50) NOT NULL,
  "password" character varying(50) NOT NULL,
  enabled boolean NOT NULL,
  CONSTRAINT users_pkey PRIMARY KEY (username)
);
CREATE TABLE authorities
(
  username character varying(50) NOT NULL,
  authority character varying(50) NOT NULL,
  CONSTRAINT fk_authorities_users FOREIGN KEY (username)
      REFERENCES users (username) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION
);

Используя встроенные реализации GrantedAuthorities, UserDetailsService и UserDetailsmanager, все в порядке. Однако я не удовлетворен реализацией JDBC Spring и хотел бы написать свои собственные. Для этого я попытался создать представление отношений следующими бизнес-объектами:

Пользовательский объект:

@Entity
@Table(name = "users", uniqueConstraints = {@UniqueConstraint(columnNames = {"username"})})
public class AppUser implements UserDetails, CredentialsContainer {

    private static final long serialVersionUID = -8275492272371421013L;

    @Id
    @Column(name = "username", nullable = false, unique = true)
    private String username;

    @Column(name = "password", nullable = false)
    @NotNull
    private String password;

    @OneToMany(
            fetch = FetchType.EAGER, cascade = CascadeType.ALL,
            mappedBy = "appUser"
    )
    private Set<AppAuthority> appAuthorities;

    @Column(name = "accountNonExpired")
    private Boolean accountNonExpired;

    @Column(name = "accountNonLocked")
    private Boolean accountNonLocked;

    @Column(name = "credentialsNonExpired")
    private Boolean credentialsNonExpired;

    @OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @JoinColumn(name = "personalinformation_fk", nullable = true)
    @JsonIgnore
    private PersonalInformation personalInformation;

    @Column(name = "enabled", nullable = false)
    @NotNull
    private Boolean enabled;

    public AppUser(
            String username,
            String password,
            boolean enabled,
            boolean accountNonExpired,
            boolean credentialsNonExpired,
            boolean accountNonLocked,
            Collection<? extends AppAuthority> authorities,
            PersonalInformation personalInformation
    ) {
        if (((username == null) || "".equals(username)) || (password == null)) {
            throw new IllegalArgumentException("Cannot pass null or empty values to constructor");
        }

        this.username = username;
        this.password = password;
        this.enabled = enabled;
        this.accountNonExpired = accountNonExpired;
        this.credentialsNonExpired = credentialsNonExpired;
        this.accountNonLocked = accountNonLocked;
        this.appAuthorities = Collections.unmodifiableSet(sortAuthorities(authorities));
        this.personalInformation = personalInformation;
    }

    public AppUser() {
    }

    @JsonIgnore
    public PersonalInformation getPersonalInformation() {
        return personalInformation;
    }

    @JsonIgnore
    public void setPersonalInformation(PersonalInformation personalInformation) {
        this.personalInformation = personalInformation;
    }

    // Getters, setters 'n other stuff

И орган власти как реализация предоставленных полномочий:

@Entity
@Table(name = "authorities", uniqueConstraints = {@UniqueConstraint(columnNames = {"id"})})
public class AppAuthority implements GrantedAuthority, Serializable {
    //~ Instance fields ================================================================================================

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.TABLE)
    @Column(name = "id", nullable = false)
    private Integer id;

    @Column(name = "username", nullable = false)
    private String username;

    @Column(name = "authority", nullable = false)
    private String authority;

    // Here comes the buggy attribute. It is supposed to repesent the
    // association username<->username, but I just don't know how to
    // implement it 
    @ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @JoinColumn(name = "appuser_fk")
    private AppUser appUser;

    //~ Constructors ===================================================================================================

    public AppAuthority(String username, String authority) {
        Assert.hasText(authority,
                "A granted authority textual representation is required");
        this.username = username;
        this.authority = authority;
    }

    public AppAuthority() {
    }

    // Getters 'n setters 'n other stuff

Моя проблема связана с @ManyToOne. из AppAuthorities: Предполагается, что это «имя пользователя», но попытка сделать это выдает ошибку, потому что я должен указать этот атрибут как String ... в то время как Hibernate ожидает связанную сущность Так что я попробовал предоставить правильную сущность и создать ассоциацию @JoinColumn(name = "appuser_fk"). Это, конечно, мусор, потому что для загрузки пользователя у меня будет внешний ключ в username, в то время как Hibernate ищет его в appuser_fk, который всегда будет пустым.

Итак, вот мой вопрос: есть ли какие-либо предложения о том, как изменить вышеупомянутый код для получения правильной реализации модели данных в JPA2?

Спасибо

Ответы [ 3 ]

8 голосов
/ 13 сентября 2010

Вам AppAuthority вообще не нужно username. Spring Security не может зависеть от этого, потому что он зависит от GrantedAuthority интерфейса , который не имеет никаких методов для доступа к имени пользователя.

Но лучше всего отделить модель вашего домена от Spring Security. Если у вас есть пользовательский UserDetailsService, вам не нужно имитировать ни схему базы данных Spring Security по умолчанию, ни ее объектную модель. Ваш UserDetailsService может загрузить ваши AppUser и AppAuthority, а затем создать UserDetails и GrantedAuthority s на их основе. Это приводит к более чистому дизайну с лучшим разделением проблем.

0 голосов
/ 27 июня 2013

Существует еще один способ, позволяющий отделить UserDetailsService от JPA / Hibernate.

Вы можете смоделировать свой класс User и Authority по своему усмотрению и использовать его при определении userDetailsService в конфигурации: -

<sec:jdbc-user-service data-source-ref="webDS"
                id="userDetailsService"
                users-by-username-query="SELECT USER_NAME,PASSWORD,TRUE FROM CUSTOMER WHERE USER_NAME=?"
                authorities-by-username-query="SELECT c.USER_NAME,r.ROLE_NAME from CUSTOMER c 
                                          JOIN CUSTOMER_ROLE cr ON c.CUSTOMER_ID = cr.CUSTOMER_ID 
                                          JOIN ROLE r ON cr.ROLE_ID = r.ROLE_ID 
                                          WHERE USER_NAME=?" />

Таким образом, вы можете определить тонко настроенный SQL-запрос для извлечения пользователей и ролей из вашей базы данных. Все, что вам нужно, - это имя таблицы и столбца.

0 голосов
/ 13 сентября 2010

Это похоже на классическую проблему Hibernate при использовании доменного ключа. Возможным исправлением было бы создание нового поля первичного ключа; например userId int для Users и Authorities сущностей / таблиц, удалите Authorities.userName и измените Users.userName на уникальный вторичный ключ.

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