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

Posted

技术标签:

【中文标题】Spring、Spring Security、JPA、MySQL、Hibernate 配置【英文标题】:Spring, Spring Security, JPA, MySQL, Hibernate Configuration 【发布时间】:2020-08-06 06:22:21 【问题描述】:

我正在开发一个使用 spring security、hibernate、JPA、mysql 的项目...我正在尝试让我的注册、登录和注销为该项目工作,并且没有任何用户输入存储在数据库中,并且无法解决这个问题来挽救我的生命。我已经尝试了几乎所有我能想到的东西,我得出的结论是,我一定错过了一些关于所有这些实际工作原理的基本信息。我不清楚我是否应该在运行项目之前创建表并在其中插入初始数据,或者表是由下面的代码创建和配置的。是否需要在我的数据库中初始创建表?如果是这样...创建这些表以便将用户输入存储在数据库中的正确方法是什么?我对@JoinTable 用户角色感到特别困惑。

package com.stephanie.mycapec.models;

import java.util.Set;
import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;

@Entity
@Table(name = "user")
public class User 
    public User() 
    
    public User(Long id, String email, String password, String fullname, boolean enabled)
        this.id = id;
        this.email=email;
        this.password=password;
        this.fullname=fullname;
        this.enabled=enabled;
    

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String email;

    private String password;

    private String fullname;

    private boolean enabled;

    @ManyToMany
    @JoinTable(name = "users_roles", joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id"))

    private Set<Role> roles;

    public Long getId() 
        return id;
    

    public void setId(Long id) 
        this.id = id;
    

    public String getEmail() 
        return email;
    

    public void setEmail(String email) 
        this.email = email;
    

    public String getPassword() 
        return password;
    

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

    public String getFullname() 
        return fullname;
    

    public void setFullname(String fullname) 
        this.fullname = fullname;
    

    public boolean isEnabled() 
        return enabled;
    

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

    public Set<Role> getRoles() 
        return roles;
    

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

    @Override
    public String toString()
        return String.format("User[id=%d, email='%s', password='%s', name='%s'" );
    




我还需要为我的数据库中的 users_roles 初始化一个表吗?还是@JoinTable 创建的架构表?

这是我运行程序时控制台的样子,它似乎与存储库有一些问题,但我也不知道如何解决这个问题。

2020-04-22 22:30:23.260  INFO 25452 --- [           main] com.stephanie.mycapec.MyCapecApp         : Starting MyCapecApp on DESKTOP-4G0GSBA with PID 25452 (C:\Users\Stephanie\My-Capec\build\classes\java\main started by Stephanie in C:\Users\Stephanie\My-Capec)
2020-04-22 22:30:23.263  INFO 25452 --- [           main] com.stephanie.mycapec.MyCapecApp         : No active profile set, falling back to default profiles: default
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by org.codehaus.groovy.vmplugin.v7.Java7$1 (file:/C:/Users/Stephanie/.gradle/caches/modules-2/files-2.1/org.codehaus.groovy/groovy/2.5.9/4222eafca660d01a44682c3fe4c629005728973/groovy-2.5.9.jar) to constructor java.lang.invoke.MethodHandles$Lookup(java.lang.Class,int)
WARNING: Please consider reporting this to the maintainers of org.codehaus.groovy.vmplugin.v7.Java7$1
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
2020-04-22 22:30:23.887  INFO 25452 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode!
2020-04-22 22:30:23.887  INFO 25452 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JDBC repositories in DEFAULT mode.
2020-04-22 22:30:23.919  INFO 25452 --- [           main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface com.stephanie.mycapec.repositories.ApdbRepository. If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2020-04-22 22:30:23.920  INFO 25452 --- [           main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface com.stephanie.mycapec.repositories.RoleRepository. If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2020-04-22 22:30:23.921  INFO 25452 --- [           main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface com.stephanie.mycapec.repositories.UseCaseRepository. If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2020-04-22 22:30:23.922  INFO 25452 --- [           main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface com.stephanie.mycapec.repositories.UserRepository. If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2020-04-22 22:30:23.922  INFO 25452 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 31ms. Found 0 JDBC repository interfaces.
2020-04-22 22:30:23.929  INFO 25452 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode!
2020-04-22 22:30:23.929  INFO 25452 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2020-04-22 22:30:23.960  INFO 25452 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 28ms. Found 4 JPA repository interfaces.
2020-04-22 22:30:24.217  INFO 25452 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration' of type [org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2020-04-22 22:30:24.388  INFO 25452 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2020-04-22 22:30:24.394  INFO 25452 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2020-04-22 22:30:24.394  INFO 25452 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.30]
2020-04-22 22:30:24.523  INFO 25452 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2020-04-22 22:30:24.523  INFO 25452 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1115 ms
2020-04-22 22:30:24.685  INFO 25452 --- [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]
2020-04-22 22:30:24.742  INFO 25452 --- [           main] org.hibernate.Version                    : HHH000412: Hibernate Core 5.4.10.Final
2020-04-22 22:30:24.832  INFO 25452 --- [           main] o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations 5.1.0.Final
2020-04-22 22:30:24.906  INFO 25452 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2020-04-22 22:30:25.208  INFO 25452 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2020-04-22 22:30:25.218  INFO 25452 --- [           main] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.MySQL5Dialect
Hibernate: alter table role_users add constraint FKipeyaf3dve9njdrl1t23ndidv foreign key (users_id) references user (id)
Hibernate: alter table role_users add constraint FKele6ufqrv6w1uoxqw6h1vkki0 foreign key (role_id) references role (id)
Hibernate: alter table users_roles add constraint FKt4v0rrweyk393bdgt107vdx0x foreign key (role_id) references role (id)
Hibernate: alter table users_roles add constraint FKgd3iendaoyh04b95ykqise6qh foreign key (user_id) references user (id)
2020-04-22 22:30:25.927  INFO 25452 --- [           main] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2020-04-22 22:30:25.933  INFO 25452 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2020-04-22 22:30:25.980  WARN 25452 --- [           main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2020-04-22 22:30:26.376  INFO 25452 --- [           main] o.s.s.web.DefaultSecurityFilterChain     : Creating filter chain: Ant [pattern='/resources/**'], []
2020-04-22 22:30:26.376  INFO 25452 --- [           main] o.s.s.web.DefaultSecurityFilterChain     : Creating filter chain: Ant [pattern='/static/**'], []
2020-04-22 22:30:26.376  INFO 25452 --- [           main] o.s.s.web.DefaultSecurityFilterChain     : Creating filter chain: Ant [pattern='/css/**'], []
2020-04-22 22:30:26.376  INFO 25452 --- [           main] o.s.s.web.DefaultSecurityFilterChain     : Creating filter chain: Ant [pattern='/js/**'], []
2020-04-22 22:30:26.376  INFO 25452 --- [           main] o.s.s.web.DefaultSecurityFilterChain     : Creating filter chain: Ant [pattern='/images/**'], []
2020-04-22 22:30:26.399  INFO 25452 --- [           main] o.s.s.web.DefaultSecurityFilterChain     : Creating filter chain: any request, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@1238a074, org.springframework.security.web.context.SecurityContextPersistenceFilter@6e12f38c, org.springframework.security.web.header.HeaderWriterFilter@5a4e492c, org.springframework.security.web.authentication.logout.LogoutFilter@240291d9, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@640a6d4b, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@5a9baba8, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@1b79df53, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@35b58254, org.springframework.security.web.session.SessionManagementFilter@26a202ae, org.springframework.security.web.access.ExceptionTranslationFilter@534d0cfa, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@49c4118b]
2020-04-22 22:30:26.517  INFO 25452 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2020-04-22 22:30:26.887  INFO 25452 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2020-04-22 22:30:26.889  INFO 25452 --- [           main] com.stephanie.mycapec.MyCapecApp         : Started MyCapecApp in 3.887 seconds (JVM running for 4.6)
Hibernate: select role0_.id as id1_1_, role0_.role as role2_1_ from role role0_ where role0_.role=?
Hibernate: select role0_.id as id1_1_, role0_.role as role2_1_ from role role0_ where role0_.role=?
2020-04-22 22:30:32.835  INFO 25452 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2020-04-22 22:30:32.835  INFO 25452 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2020-04-22 22:30:32.842  INFO 25452 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 7 ms
2020-04-22 22:30:33.050  WARN 25452 --- [nio-8080-exec-1] o.a.c.util.SessionIdGeneratorBase        : Creation of SecureRandom instance for session ID generation using [SHA1PRNG] took [175] milliseconds.
2020-04-22 22:30:33.421  WARN 25452 --- [nio-8080-exec-2] n.n.u.t.expressions.ExpressionProcessor  : Fragment expression "default" is being wrapped as a Thymeleaf 3 fragment expression (~...) for backwards compatibility purposes.  This wrapping will be dropped in the next major version of the expression processor, so please rewrite as a Thymeleaf 3 fragment expression to future-proof your code.  See https://github.com/thymeleaf/thymeleaf/issues/451 for more information.
Hibernate: select user0_.id as id1_4_, user0_.email as email2_4_, user0_.enabled as enabled3_4_, user0_.fullname as fullname4_4_, user0_.password as password5_4_ from user user0_ where user0_.email=?

这也是我的 application.properties

spring.datasource.url = jdbc:mysql://localhost:3306/login?useSSL=true
spring.datasource.username = root
spring.datasource.password = uber
spring.datasource.testWhileIdle = true
spring.datasource.validationQuery = SELECT 1
spring.jpa.show-sql = true
spring.jpa.hibernate.ddl-auto = update
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
spring.jpa.database=H2
spring.jpa.properties.hibernate.jdbc.batch_size=5
spring.jpa.properties.hibernate.order_inserts=true
spring.jpa.properties.hibernate.order_updates=true
spring.jpa.properties.hibernate.jdbc.batch_versioned_data=true

如果有人能帮我弄清楚如何将用户输入存储在数据库中,以便我的登录和身份验证在我的 spring 项目中正常工作,我将永远感激不尽。

编辑 根据要求,这是我的角色实体

package com.stephanie.mycapec.models;
import java.util.Set;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.Table;

@Entity
@Table(name = "role")
public class Role 

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String role;

    @ManyToMany(targetEntity= User.class)
    //@ManyToMany(mappedBy = "role")
    private Set<User> users;

    public Long getId() 
        return id;
    

    public void setId(Long id) 
        this.id = id;
    

    public String getRole() 
        return role;
    

    public void setRole(String role) 
        this.role = role;
    

    public Set<User> getUsers() 
        return users;
    

    public void setUsers(Set<User> users) 
        this.users = users;
    


【问题讨论】:

您能否为您的用户实体提供完整的代码?以及你的角色实体?另外,您是如何填充和保存实体的? 所以,我尝试在 intellij 和 mysql 的数据库窗口中创建数据库并将它们填充到控制台中,但似乎两种方法都不起作用。应该发生的是用户完成了注册过程,并且用户名和密码应该进入数据库,但它不起作用。我尝试在我的资源文件夹中创建一个 sql 文件来创建和插入测试数据,这确实有效,但是任何应该存储的用户数据都丢失了。我对是否需要预先创建这些表或实体是否创建表感到困惑。 如果我应该手动创建表,那么我必须使用错误的数据类型或外键。如果你想看的话,我也有一个 UserDetailService。我有一个包含所有代码的 github 存储库,如果这能让你更轻松......非常感谢你的回答 github.com/srimel1/CAPEC 【参考方案1】:

我不确定这是否可行。让我从一些关于如何为程序和数据库建模的一般想法开始。

首先,创建数据库并对表和与之相关的所有内容进行建模非常重要。考虑一下您希望以何种数据类型存储在数据库中的内容以及它们之间的关系。仅当您的数据库模型完全建立时,然后尝试根据您的数据库对您的 java 对象进行建模。这也意味着您必须在尝试存储数据之前创建所有表。

所以你必须有两个看起来像这样的表

用户:

id         bigint    not null    autoincrement
email      varchar   not null
password   varchar   not null
fullname   varchar   not null
enabled    boolean   not null    default false

角色:

id         bigint    not null    autoincrement
role       varchar   not null

这里的重点是你不能存储用户和他所拥有的角色之间的任何关系。在您的类中,您已经为多对多关系建模,但数据库无法存储它。

我对这个问题的解决方案是第三个表格,看起来像这样

用户角色:

id         bigint    not null    autonincrement
userid     bigint    not null
roleid     bigint    not null

当然这个表必须有两个外键约束。 考虑到这一点,您的课程可能如下所示:

用户:

  @Entity
  @Table(name="user")
  public class User implements Serializable 

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name="id")
  private Long id;

  @Column(name="fullname")
  private String fullname;

  @Column(name="password")
  private String password;

  @Column(name="enabled")
  private Boolean enabled;

  @Column(name="email")
  private String email;

  @JsonManagedReference
  @OneToMany(mappedBy="userid", cascade=CascadeType.ALL)
  @Fetch(value = FetchMode.SELECT)
  private List<UserRoles> roles = null;

  // Gettes & Setters etc.


角色:

@Entity
@Table(name="role")
public class UserRole implements Serializable 

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name="id")    
  private Long id;

  @Column(name="name")
  private String name;

  @JsonBackReference
  @OneToMany(mappedBy="roleid")
  @Fetch(value = FetchMode.JOIN)
  private List<UserRoles> users;

  // Getters & Setters


用户角色:

@Entity
@Table(name="userrole")
public class UserRoles implements Serializable

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name="id")
  private Long id;

  @JsonBackReference
  @ManyToOne
  @JoinColumn(name="user_id")
  private User userid;

  @JsonManagedReference
  @ManyToOne
  @JoinColumn(name="role_id")
  private UserRole roleid;

  // Getters & Setters


当然,我在课堂上跳过了一些部分,但建模是这样工作的。您现在有一个将关系存储在数据库中的位置,并且您已经在程序中对此进行了建模。 根据您的需要检查和设置 JSON 设置和获取策略。

【讨论】:

【参考方案2】:

我想我现在看到了代码的问题。你应该从你的 application.properties

中删除它
spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true

并将@Transactional 注释添加到您的CustomUserDetailService

@Override
@Transactional
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException 

    User user = userRepository.findByEmail(email);  
    if(user != null) 
        List<GrantedAuthority> authorities = getUserAuthority(user.getRoles());
        return buildUserForAuthentication(user, authorities);
     else 
        throw new UsernameNotFoundException("username not found");
    

如果您正在使用 hsqldbh2 等嵌入式(内存中)数据库进行测试,那么 Spring 将自动为您创建数据库表。对于其他数据库,如 mySql 或 PostgreSql,您必须明确指示 Hibernate(通过 Spring)创建或更新模式对象。这是通过将此属性添加到您的 application.properties 来完成的:spring.jpa.hibernate.ddl-auto = update

至于将用户与角色相关联,是的,您必须通过手动插入或在应用程序中创建管理用户和角色的管理仪表板来执行此操作。 (典型的企业应用)

在这种特殊情况下,Hibernate 将创建三个表,分别称为 userroleusers_roles。最后一个是因为ManyToMany关联,所以将用户与角色关联起来。

您的代码已经在登录过程中填充了用户,然后您必须通过我提到的管理仪表板或手动插入以某种方式向每个用户授予适当的角色。

我希望这会有所帮助。

【讨论】:

是的,这确实很有帮助,谢谢。不幸的是,我仍然无法弄清楚我的代码有什么问题,以及为什么在我注册时似乎没有将用户添加到我的 sql 数据库中。我想知道我的html是否有问题?我找不到问题,我已经为此工作了好几个星期了。 我更新了我的答案。希望这可以为您解决问题。 做到了!你太棒了! 我很高兴它成功了。您收到异常的原因是您的数据库事务在执行路径的后期划分,并且您在会话关闭且事务完成时尝试访问相关实体。通过在访问父实体(用户)和关联实体(角色)的方法中添加@Transactional,您的会话保持打开状态,然后您就可以获取相关实体。

以上是关于Spring、Spring Security、JPA、MySQL、Hibernate 配置的主要内容,如果未能解决你的问题,请参考以下文章

Spring Security:2.4 Getting Spring Security

Spring mvc / security:从spring security中排除登录页面

没有 JSP 的 Spring Security /j_spring_security_check

Spring-Security

Spring Security 登录错误:HTTP 状态 404 - /j_spring_security_check

Spring 框架 4.0 和 Spring security 3.2.4 上的 Spring Security SAML 扩展