【问题标题】:Howto implement Spring Security User/Authorities with Hibernate/JPA2?如何使用 Hibernate/JPA 2 实现 Spring Security 用户/权限?
【发布时间】:2011-04-11 10:24:36
【问题描述】:

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

repesented as postgresql create 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
);

使用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 实现的任何建议?

谢谢

【问题讨论】:

    标签: java hibernate orm jpa spring-security


    【解决方案1】:

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

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

    【讨论】:

    • 啊!这真是个好消息!我会按照你的建议进行,谢谢。
    • 可以分享项目地址吗?我已经浪费了我 5 天的时间来使用带有 oauth2 的 spring boot security rest api 制作一个模块。问题是当我调用 url 时会给出虚假数据,例如“authorities”:null、“accountNonExpired”:false、“accountNonLocked”:false、“credentialsNonExpired”:false、“enabled”:false。
    【解决方案2】:

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

    【讨论】:

    • 嗨。你的解决方案是我的第一个猜测,如果不是 Spring,我会使用它(实际上,如果我设计了这个数据模型,我会总是使用 int ids)。但是,我担心所有这些约定优于配置,removing 属性可能会对 Spring Security Framework 的其余部分产生不可预知的副作用。如果一些Spring Sec怎么办。 bean 依赖于 Authorities.userName?当然,我可以重新实现它们,但是没有其他解决方案可以让我的数据模型保持不变吗?总而言之,我确实希望 ORM 框架能够采用给定的数据模型,而不是相反...
    【解决方案3】:

    还有一种方法可以将 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 查询以从数据库中获取用户和角色。 您只需要注意表名和列名。

    【讨论】:

      猜你喜欢
      • 2012-12-22
      • 1970-01-01
      • 2016-09-18
      • 2015-01-28
      • 2020-10-14
      • 2011-05-22
      • 2021-02-27
      • 2013-07-07
      • 2010-10-27
      相关资源
      最近更新 更多