我正在尝试实现DAO以在Hibernate / JPA2中使用Spring Security数据库身份验证。 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
);
使用GrantedAuthorities
,UserDetailsService
和UserDetailsmanager
的板载实现,一切都很好。但是,我对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实现的任何建议?
由于
答案 0 :(得分:8)
AppAuthority
根本不需要username
。 Spring Security不能依赖它,因为它取决于没有任何方法来访问用户名的GrantedAuthority
interface。
但更好的做法是将您的域模型与Spring Security分离。如果您有自定义UserDetailsService
,则无需模拟Spring Security的默认数据库架构及其对象模型。您的UserDetailsService
可以加载您自己的AppUser
和AppAuthority
,然后根据它们创建UserDetails
和GrantedAuthority
。这样可以实现更清晰的设计,更好地分离关注点。
答案 1 :(得分:0)
这看起来像使用特定于域的密钥的经典Hibernate问题。可能的解决方法是创建一个新的主键字段;例如对于userId int
和Users
个实体/表格Authorities
,请移除Authorities.userName
,然后将Users.userName
更改为唯一的辅助密钥。
答案 2 :(得分:0)
还有一种方法可以将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查询,以从数据库中获取用户和角色。 您只需要处理表和列名称。