如何使用Hibernate / JPA2实现Spring Security用户/权限?

我正在尝试实现DAO以在Hibernate / JPA2中使用Spring Security数据库身份validation。 Spring使用以下关系和关联来表示用户和角色:

替代文字

作为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实现并不满意,并且想编写自己的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 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 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 usernameusername, 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实现的任何建议?

谢谢

AppAuthority根本不需要username 。 Spring Security不能依赖它,因为它依赖于GrantedAuthority接口 ,该接口没有任何访问用户名的方法。

但更好的做法是将您的域模型与Spring Security分离。 当您拥有自定义UserDetailsService ,您不需要模仿Spring Security的默认数据库架构及其对象模型。 您的UserDetailsService可以加载您自己的AppUserAppAuthority ,然后根据它们创建UserDetailsGrantedAuthority 。 这导致更清晰的设计,更好地分离关注点。

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

还有一种方法可以将UserDetailsS​​ervice与JPA / Hibernate分离。

您可以根据需要为User和Authority类建模,并在配置中定义userDetailsS​​ervice时使用它: –

  

这样,您可以定义精细调整的SQL查询,以从数据库中获取用户和角色。 您需要注意的是表和列名称。