Spring Security + JPA 用户模式

Posted

技术标签:

【中文标题】Spring Security + JPA 用户模式【英文标题】:Spring Security + JPA user schema 【发布时间】:2013-05-01 03:44:12 【问题描述】:

根据 Spring 文档,如果您需要通过数据库管理 Spring 安全性,您应该有一些标准的表模式。例如。

create table users(
    username varchar(256) not null primary key,
    password varchar(256) not null,
    enabled boolean not null
);

create table authorities (
    username varchar(256) not null,
    authority varchar(256) not null,
    constraint fk_authorities_users foreign key(username) references users(username)
);
create unique index ix_auth_username on authorities (username,authority);

我面临的问题如下。 1) 无法理解如何使用 JPA 实现这种表模式?

我尝试了以下方法。

@Entity
@Table(name="USERS")
public class UsersPersistence extends Users implements Serializable

    private static final long serialVersionUID = 1009548075747154488L;

    public UsersPersistence() 
        super();
    

    public UsersPersistence(long id, String userName, String password, boolean enabled) 
        super(id, userName,password,enabled);
    

    @Id
    @GeneratedValue
    @Column(name="id")
    @Override
    public long getId() 
        return super.getId();
    

    @Column(name="username", nullable=false)
    @Override
    public String getUserName() 
        return super.getUserName();
    

    @Column(name="password", nullable=false)
    @Override
    public String getPassword() 
        return super.getPassword();
    

    @Column(name="enabled", nullable=false)
    @Override
    public boolean isEnabled() 
        return super.isEnabled();
    


此表根据 Spring 文档架构中所述的要求创建。 理解的问题是当我试图在权限表中的用户名上分配外键时。由于 JPA 通过父表(主键表)的 id 分配外键,或者我可能不知道如何分配它。

以下是产生问题的 JPA 类:-

@Entity
@Table(name="AUTHORITIES")
public class AuthoritiesPersistence extends Authorities implements Serializable

    private static final long serialVersionUID = 1L;

    public AuthoritiesPersistence() 
        super();
    

    public AuthoritiesPersistence(long id, UsersPersistence userName, String authority) 
        super(id,userName,authority);
    

    @Id
    @GeneratedValue
    @Column(name="id")
    @Override
    public long getId() 
        return super.getId();
    

    @Override
    @ManyToOne(cascade=CascadeType.ALL)
    @JoinColumn(name="username", nullable=false)
    public UsersPersistence  getUserName() 
        return (UsersPersistence) super.getUserName();
    

    @Column(name="authority", nullable=false)
    @Override
    public String getAuthority() 
        return super.getAuthority();
    


此表创建成功,但 Spring 安全认证无法识别用户名,因为 JPA 使用外键 id 而不是实际用户名。

任何帮助都将不胜感激。我真的被困在创建一个基于用户名而不是 id 的外键。 谢谢

【问题讨论】:

您的表中没有指定 id。 首先感谢您的快速回复@Kelvin。两个表中都指定了 ID。两个持久性类都扩展了一些父类。这些类有 id 并且在持久性类中我只是覆盖那些 getter 并将它们分配为 table id 列。 但是 ddl 没有提到 id 列 绝对正确。 DDL 没有提到 id 列。那就是问题所在。如果没有 id,我如何分配外键。 JPA 中是否可以根据 DDL 分配这样的外键?或者我没有其他选择,只能手动创建表模式并使用 JDBC dao 而不是 JPA Dao 类? 【参考方案1】:

当您使用默认的JdbcDaoImpl 作为UserDetailsService 实现时,您只需遵守Spring Security 参考文档中给出的架构,如果您的安全配置中有<jdbc-user-service> 标记,就是这种情况。即使这样,也可以覆盖用于获取用户和权限的默认 SQL 查询(请参阅namespace appendix)。

但是,如果您使用 hibernate 管理用户帐户,编写自己的 UserDetailsService 实现而不是尝试创建导致 JdbcDaoImpl 所需的特定架构的 JPA 实体是有意义的。

documentation 也表示:

如果您的应用程序确实使用 ORM 工具,您可能更愿意编写自定义 UserDetailsS​​ervice 来重用您可能已经创建的映射文件。

【讨论】:

感谢您的回复@zagyi。根据规范,您不能更改架构。问题从来不在于规范所说的内容。问题是有什么可能的方法可以通过 JPA 实现该模式。 我的意思是说,在你的情况下,忘记使用<jdbc-user-service> 可能更容易,它是默认模式。使用 hibernate 实现您自己的 UserDetailsService 几乎是微不足道的,在任何情况下都不会将其视为 hack 或实现身份验证的非标准方式。【参考方案2】:

我已经找到了替代方案,即“配置 JdbcUserDetailsManager 以使用自定义 SQL 查询”至少我可以通过 JPA 创建我的表并且可以希望 “ users-by-username-query 和 authority-by-username-query ”确实可以完成我的工作。 为了实现它,我必须添加以下架构。

create table custom_user_authorities (
    id bigint identity,
    user bigint not null,
    authority varchar(256) not null,
);

此架构有 id(将自动递增),它肯定适用于 JPA。

【讨论】:

【参考方案3】:

有几种方法可以解决这个问题:

您可以使用 Hibernate 编写自己的 AuthenticationProvider 数据库访问 您可以使用<jdbc-user-service> 并覆盖@zagyi 提到的SQL 查询。 (http://static.springsource.org/spring-security/site/docs/current/reference/appendix-namespace.html#nsa-jdbc-user-service) 或者您可以创建适合标准<jdbc-user-service> 的架构

要使用您感兴趣的方法,您必须知道,除了拥有用户名、密码和启用的 Spring Security 字段之外,还希望用户名是唯一标识符。这意味着您可以使用实体的用户名属性作为数据库和 Hibernate 的 ID。 如果您不想这样做,一种解决方法是设置一个表,其中使用 ID/名称和权限定义权限。然后设置使用可连接将它们映射到用户。 一些未经测试的示例代码: 角色:

@Entity
@DynamicUpdate
@Table(name ="authorities")
public class Authority


    private String authority;

    @Id
    @Column(name="authority")
    public String getAuthority() 
        return authority;
    

用户:

@Entity
@DynamicUpdate
@Table(name = "users", uniqueConstraints= @UniqueConstraint(columnNames="username"))
public class User 
private String username;
private List<Authority> authorities;
@Type(type = "numeric_boolean")
    private boolean enabled;


        @Id
    @Column(name="username")
    public String getUsername() 
        return username;
    

        @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(
       name = "authorities",
       joinColumns = @JoinColumn(name = "username"), 
       inverseJoinColumns = @JoinColumn(name = "rolename")
     )
    public List<Authority> getauthorities() 
        return authorities;
    

        @Column(name="ENABLED")
    public boolean isEnabled() 
        return enabled;
    

当基础运行时,您可以根据需要添加内部使用的属性。

【讨论】:

【参考方案4】:

我能够使用以下类定义映射这些表:

@Entity
@Table(name = "users")
public class User 

    @Id
    @Column(name = "username")
    private String username;
    @Column(name = "password")
    private String password;
    @Column
    private boolean enabled;
    @Column
    private String firstName;
    @ElementCollection
    @JoinTable(name = "authorities", joinColumns = @JoinColumn(name = "email"))
    @Column(name = "authority")
    private Set<String> roles;

    public User() 
    

    public Serializable getId() 
        return username;
    

    public String getUsername() 
        return username;
    

    public String getPassword() 
        return password;
    

    public boolean isEnabled() 
        return enabled;
    

    public void setEnabled(boolean enabled) 
        this.enabled = enabled;
    

    public void setPassword(String password) 
        this.password = password;
    

    public void setUsername(String username) 
        this.username = username;
    

    public String getPassword() 
        return password;
    

    public void setPassword(String password) 
        this.password = password;
    

    public Set<String> getRoles() 
        return roles;
    

    public void setRoles(Set<String> roles) 
        this.roles = roles;
    


【讨论】:

以上是关于Spring Security + JPA 用户模式的主要内容,如果未能解决你的问题,请参考以下文章

使用spring boot、jpa和security的多用户restful api

Spring、Spring Security、JPA、MySQL、Hibernate 配置

Spring Security 3.1 + JPA - 空指针异常

Spring Security 3 身份验证与 Hibernate 3(JPA) 注释的集成

具有 Security EvaluationContextExtensionSupport 的 Spring Data JPA 不起作用

Spring + Testcontainers + Jpa + 具有多个用户/模式的 Oracle 数据库