2010-09-12 116 views
7

我正试图在Hibernate/JPA2中使用Spring Security数据库身份验证来实现DAO。 Spring使用下面的关系和关联,以表示用户&角色:如何使用Hibernate/JPA2实现Spring Security用户/权限?

alt text

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

使用的GrantedAuthoritiesUserDetailsServiceUserDetailsmanager板载实现,一切很好。但是,我对Spring的JDBC实现并不满意,并想写我自己的。为了做到这一点,我试图通过以下业务对象来创建关系的表示:

用户实体:

@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 

和管理局实体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 

我的问题是@ManyToOne assoc。 AppAuthorities:它应该是“用户名”,但尝试和这样做会引发错误,因为我必须将该属性表示为String ......而Hibernate期望关联的实体。所以我尝试的实际上是提供了正确的实体,并通过@JoinColumn(name = "appuser_fk")创建关联。这当然是垃圾,因为为了加载用户,我将在username中有外键,而Hibernate在appuser_fk中搜索它,它总是空的。

所以,这里是我的问题:关于如何修改上面提到的代码以获得数据模型的正确JPA2实现的任何建议?

感谢

回答

8

AppAuthority完全不需要username。 Spring Security不能依赖它,因为它取决于GrantedAuthority interface,它没有任何访问用户名的方法。

但更好的做法是将您的域模型与Spring Security分离。当你有一个自定义的UserDetailsService时,你不需要模拟Spring Security的默认数据库模式和它的对象模型。您的UserDetailsService可以加载您自己的AppUserAppAuthority,然后根据它们创建UserDetailsGrantedAuthority。这导致更清洁的设计,更好地分离关注点。

+0

啊!这真是个好消息!我会按照你的建议进行,谢谢。 – 2010-09-13 13:50:02

+0

你可以分享项目的网址吗?我已经浪费了5天时间,使用spring引导安全rest api和oauth2来创建一个模块。问题是当我打电话给URL是给虚假的数据,如“权限”:null, “accountNonExpired”:false, “accountNonLocked”:false, “credentialsNonExpired”:false, “enabled”:false。 – Harsh 2017-10-17 06:39:25

0

这看起来像使用特定域密钥的经典Hibernate的问题。可能的解决方法是创建一个新的主键字段;例如userId intUsersAuthorities实体/表,删除Authorities.userName,并将Users.userName更改为唯一的辅助密钥。

+1

嗨。你的解决方案是我的第一个猜测,如果它不是Spring,我会用它(实际上,如果我设计了这个数据模型,我总是*使用int ID)。但是,我担心,通过所有的约定配置,remove *属性可能会对Spring Security Framework的其他部分产生不可预知的副作用。如果某个Spring Sec会发生什么呢? bean依赖Authorities.userName?当然,我可以重新实现它们,但是没有其他解决方案保持我的数据模型不变吗?总而言之,我确实希望ORM框架能够采用给定的数据模型,而不是其他方式... – 2010-09-12 23:00:18

0

还有一种方法可以将UserDetailsS​​ervice从JPA/Hibernate中分离出来。

,你喜欢,你可以模拟你的用户和机构类,并使用此,而在配置中定义的UserDetailsS​​ervice: -

<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查询来从数据库中提取用户和角色。 所有你需要照顾的是表格和栏目名称。