2010-09-12 1 views
7

Sto cercando di implementare DAO per lavorare con l'autenticazione del database Spring Security in Hibernate/JPA2. Spring usa seguenti relazioni e le associazioni al fine di rappresentare utente & ruoli:Come implementare utenti/autorità di sicurezza Spring con Hibernate/JPA2?

alt text

repesented come PostgreSQL creare query:

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 
); 

Utilizzando le implementazioni a bordo di GrantedAuthorities, UserDetailsService e UserDetailsmanager, tutto è ok. Tuttavia, non sono soddisfatto dell'implementazione JDBC di Spring e vorrei scrivere i miei. Per fare ciò, ho cercato di creare una rappresentazione dei rapporti da seguenti oggetti di business:

L'entità utente:

@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 

e l'entità autorità come l'implementazione di GrantedAuthorities:

@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 

Il mio problema è l'associazione @ManyToOne. di AppAuthorities: Si suppone che sia "nomeutente", ma provare a farlo genera un errore, perché devo digitare quell'attributo come String ... mentre Hibernate si aspetta l'entità associata. Quindi quello che ho provato è in realtà fornire l'entità corretta e creare l'associazione per @JoinColumn(name = "appuser_fk"). Questo è, naturalmente, spazzatura, perché per caricare l'utente, avrò la chiave esterna username, mentre ricerca Hibernate per esso in appuser_fk, che saranno sempre vuota.

Quindi, ecco la mia domanda: qualche suggerimento su come modificare il codice sopra menzionato al fine di ottenere una corretta implementazione JPA2 del modello di dati?

Grazie

risposta

8

È AppAuthority non ha bisogno username affatto. Spring Security non può dipendere da esso perché dipende dallo GrantedAuthority interface che non ha alcun metodo per accedere al nome utente.

Ma la pratica migliore è quella di separare il modello di dominio dalla Primavera di sicurezza. Quando si dispone di una personalizzazione UserDetailsService, non è necessario imitare né lo schema del database predefinito di Spring Security né il relativo modello a oggetti. Il tuo UserDetailsService possibile caricare il proprio AppUser e AppAuthority e quindi creare UserDetails e GrantedAuthority s basati su di essi. Ciò porta a un design più pulito con una migliore separazione delle preoccupazioni.

+0

Ah! Questa è una fantastica notizia! Procederò esattamente come suggerisci, grazie. –

+0

puoi condividere l'URL del progetto? Ho già sprecato i miei 5 giorni per creare un modulo utilizzando api di sicurezza di avvio a molla con oauth2. il problema è quando chiamo url sta dando i dati falsi come "autorità": null, "accountNonExpired": false, "accountNonLocked": false, "credentialsNonExpired": false, "abilitati": false. – Harsh

0

Questo appare come il classico problema di Hibernate utilizzando una chiave specifica del dominio. Una possibile soluzione sarebbe quella di creare un nuovo campo chiave primaria; per esempio. userId int per le Users e Authorities entità/tavoli, rimuovere Authorities.userName, e cambiare Users.userName a una chiave secondaria unica.

+1

Ciao. La tua soluzione era la mia prima ipotesi e, se non fosse stato per Spring, l'avrei usata (in realtà, se avessi progettato questo modello di dati, avrei * sempre * usato int ids). Tuttavia, temo che con tutte le convenzioni sulla configurazione, gli attributi * remove * possano avere effetti collaterali imprevedibili sul resto del Spring Security Framework. Cosa succede se qualche primavera sec. i fagioli si basano su Authorities.userName? Certo, potrei ri-implementarli, ma non c'è altra soluzione che lascia intatto il mio modello di dati? Tutto sommato, mi aspetto che un framework ORM si adatti a un dato modello di dati, e non viceversa ... –

0

C'è un altro modo che disaccoppia l'UserDetailsService da JPA/Hibernate.

È possibile modellare il vostro utente e la classe di Autorità come ti piace e utilizzare questo mentre la definizione di userDetailsService in configurazione: -

<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=?" /> 

In questo modo è possibile definire query SQL perfezionato per andare a prendere e dei ruoli dal database. Tutto ciò di cui hai bisogno è il nome della tabella e della colonna.