2010-09-12 20 views
7

Estoy tratando de implementar DAO para trabajar con la autenticación de base de datos Spring Security en Hibernate/JPA2. Primavera utiliza siguientes relaciones y asociaciones con el fin de representar usuario & papeles:¿Cómo implementar Spring Security User/Authorities con Hibernate/JPA2?

alt text

repesented como PostgreSQL Crear una consulta:

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

Usando las implementaciones de a bordo de GrantedAuthorities, UserDetailsService y UserDetailsmanager, todo está bien. Sin embargo, no estoy satisfecho con la implementación de JDBC de Spring y me gustaría escribir las mías. Con el fin de hacerlo, he tratado de crear una representación de las relaciones siguiendo los objetos de negocio:

La entidad de usuario:

@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 

y la entidad autoridad como una implementación de 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 

Mi problema es el @ManyToOne assoc. de AppAuthorities: se supone que es "nombre de usuario", pero intentarlo arroja un error, porque tengo que tipificar ese atributo como String ... mientras que Hibernate espera la entidad asociada. Entonces, lo que intenté es proporcionar la entidad correcta y crear la asociación por @JoinColumn(name = "appuser_fk"). Esto es, por supuesto, basura, porque para cargar el usuario, tendré la clave foránea en username, mientras que Hibernate la busca en appuser_fk, que siempre estará vacía.

Así que aquí está mi pregunta: ¿alguna sugerencia sobre cómo modificar el código mencionado anteriormente con el fin de obtener una implementación correcta de JPA2 del modelo de datos?

Gracias

Respuesta

8

Usted AppAuthority no necesita username en absoluto. Spring Security no puede depender de él porque depende del GrantedAuthority interface que no tiene ningún método para acceder al nombre de usuario.

Pero la mejor práctica es desacoplar su modelo de dominio de Spring Security. Cuando tiene un UserDetailsService personalizado, no necesita imitar ni el esquema de base de datos predeterminado de Spring Security ni su modelo de objetos. Su UserDetailsService puede cargar su propio AppUser y AppAuthority y luego crear UserDetails y GrantedAuthority s basados ​​en ellos. Esto lleva a un diseño más limpio con una mejor separación de preocupaciones.

+0

¡Ah! ¡Esas son buenas noticias! Procederé exactamente como sugieres, gracias. –

+0

¿puedes compartir la URL del proyecto? Ya perdí mis 5 días para hacer un módulo usando la aplicación de seguridad de reposo de primavera api con oauth2. el problema es cuando llamo url está dando datos falsos como "autoridades": null, "accountNonExpired": falso, "accountNonLocked": falso, "credentialsNonExpired": falso, "enabled": falso. – Harsh

0

Esto se ve como el problema de hibernación clásica de utilizar una clave de dominio específico. Una posible solución sería crear un nuevo campo de clave principal; p.ej. userId int para las entidades/tablas Users y Authorities, elimine Authorities.userName, y cambie Users.userName a una clave secundaria única.

+1

Hola. Su solución fue mi primera suposición, y si no fuera por Spring, la habría utilizado (de hecho, si hubiera diseñado este modelo de datos, siempre * usaría * IDs int). Sin embargo, me temo que con todos los atributos de convención sobre configuración, * eliminar * puede tener efectos secundarios impredecibles en el resto del Marco de seguridad de Spring. ¿Qué pasa si algún Spring Sec. los frijoles confían en Authorities.userName? Claro, podría volver a implementarlos, pero ¿no hay otra solución que deje intacto mi modelo de datos? Con todo, espero que un marco ORM se adapte a un modelo de datos dado, y no al revés ... –

0

Hay una forma más de desacoplar el UserDetailsService de JPA/Hibernate.

Puede modelar el usuario y la clase Autoridad como desee y utilizar esto mientras se define userDetailsService de configuración: -

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

De esta manera se puede definir afinado consulta SQL a buscar el usuario y las funciones de su base de datos. Todo lo que necesita para cuidar es el nombre de la tabla y la columna.

Cuestiones relacionadas