在 Spring Security 中添加新用户时出现异常

Posted

技术标签:

【中文标题】在 Spring Security 中添加新用户时出现异常【英文标题】:Getting Exception while adding new User in Spring Security 【发布时间】:2020-10-24 05:16:09 【问题描述】:

我有一个用户类和角色类和用户角色类。现在,每次我尝试添加一个具有一组已经存在的角色的新用户时,它都会引发一个正确的唯一错误。但理想情况下,如果新角色已经存在,它不应该尝试保存它。下面我添加了我所有的表格和保存方法。

@Table(name = "t_user")
@Data
public class User 

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

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

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

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

    @Size(max = 100)
    @NotBlank(message = "Last name can not be empty")
    @Column(name = "last_name")
    private String lastName;

    @Column(name = "is_archived")
    private Boolean isArchived = false;

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

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

    @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinTable(name = "t_user_role", joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"),
            inverseJoinColumns = 
                    @JoinColumn(name = "role_id", referencedColumnName = "id"))
    private Set<Role> roles = new HashSet<>();




@Data
@Table(name = "m_role")
@Entity
public class Role 

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

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



@Data
@Table(name = "t_user_role")
@Entity
public class UserRole 

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

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

    @ManyToOne
    @JoinColumn(name = "role_id")
    private Role role;

我保存用户的方法:

User newUser = new User();
        newUser.setFirstName(user.getFirstName());
        newUser.setLastName(user.getLastName());
        newUser.setEmail(user.getEmail());
        newUser.setMobileNumber(user.getPassword());
        newUser.setPassword(bcryptEncoder.encode(user.getPassword()));
        newUser.setRoles(user.getRoles());
        return userRepository.save(newUser);
    

以下是创建用户的post请求格式:


    "firstName":"first",
    "lastName":"name",
    "email":"email@gmail.com",
    "mobileNumber":"1110122223",
    "password":"1234567890",
    "roles":[
        "name":"ADMIN"
    ]

我不想插入应该是理想的角色(如果存在)。但这是我在使用角色实现 Spring Security 时发现的标准方式

【问题讨论】:

你能添加例外吗?没有它我们就没有想法 Lombok @Data 包含 @EqualsAndHashCode(需要一个 Set)。你不希望出现这种情况。在您的课程中,键入 alt-insert 并实现 Equals() 和 HashCode()。可能还有另一个问题,但这引起了我的注意。 thorben-janssen.com/… 即将到来的异常是数据完整性违规异常,因为角色管理员已经存在 使用save 方法调用会在数据库中插入记录,并且由于唯一性约束会抛出错误。如果您想防止重复值,那么您可以在调用保存方法之前检查并显示您的自定义消息 但是我在 t_user_role 表而不是角色表上进行连接调用 【参考方案1】:

您的请求缺少id 的角色。因为id 不存在。 Spring尝试在role表中添加一个新角色。


    "firstName":"first",
    "lastName":"name",
    "email":"email@gmail.com",
    "mobileNumber":"1110122223",
    "password":"1234567890",
    "roles":[
        "id" : "" // ID must be present here.
        "name":"ADMIN"
    ]

或者从role -&gt; name,你可以从角色表中获取Role entity/obj,并设置在User对象中。

[更新 2]:

@ManyToMany(cascade = CascadeType.DETACH, fetch = FetchType.LAZY)
  @ToString.Exclude
  private Set<Role> roles = new HashSet<>();

您需要将级联类型从“ALL”更改为“DETACH”。见:ALL 表示如果你保存 USER,ROLE 也会被保存,如果你删除 USER,角色也应该被删除。这不是我们想要的。您只需要使用“ROLE”,无需以任何方式操作“ROLE”表记录。

【讨论】:

org.hibernate.PersistentObjectException:分离的实体传递给持久化:com.user.model.Role。我在持久化对象时遇到了这个异常 就在请求正文中,我添加了存在的 id 并尝试拨打休息电话 是写,当你尝试保存用户时,spring会自动尝试保存角色(如果角色的id不存在->尝试添加新记录),(如果id存在->尝试更新记录)。我们需要将用户中的角色字段标记为“DETACH”。为此->您需要更改角色集上的@ManytoMany 注释内的级联类型。 [cascade = CascadeType.DETACH] 请添加评论以否决答案。【参考方案2】:

根据我的理解,您的要求是:

    用户实体 角色实体,每个用户都有多个角色 如果角色是从客户端传递的,您希望仅当数据库中不存在该角色时才保存该角色,否则您想使用现有角色(更新:根据 cmets 和我的意见,永远不会理想的事情)

在您的情况下,我建议让 Spring 处理 User->Roles 关系,如下所示:

public class User 

  ... all fields
  @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
  @ToString.Exclude
  private Set<Role> roles = new HashSet<>();


public class Role 
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    private String name;

在角色存储库中,您需要一个方法:Optional&lt;Role&gt; findByName(String Name);

在层之间(最好在服务层),试试这个:

public Map<String, Object> addUser(User user) 
    // perform validations

    user.getRoles().forEach(role -> 
        Role existing = roleRepository.findByName(role.getName())
                       .orElse(new Role(role.getName())); // using optional to create new role with name passed in from the client
        if (existing.getId() != null) user.setRole(existing); // UPDATE: NOT IDEAL
    );
    
    
    ... other tasks
    userRepository.save(user); // this all saves the correct role
    return yourResponseMap;

其他说明:

    我们通常更喜欢保持 fetches Lazy,而不是 Eager。但在某些情况下,您可能需要 Eager 检索,这取决于您。 在我看来,让 Spring Data JPA 处理第三个表更方便。 org.hibernate.PersistentObjectException: detached entity passed to persist 发生在您尝试直接保存从客户端传入的角色而不将其加载到您的应用程序时(请参阅“添加用户”的服务层方法)。 查看此link,您可能会发现它很有帮助。

【讨论】:

你的addUser函数有两个问题,首先它不支持一个用户的多个角色,这是一个小问题。其次,我们不应该在添加用户时添加新角色。角色通常只是预定义的。这似乎属于重大问题。 我很抱歉,但您的回答对解决问题没有任何帮助。您刚刚添加了额外的“懒惰”加载。在这种情况下仍然没有帮助。用户必须具有角色。 'LAZY' 我们用在我们有额外属性并且可能有大量集合或有变大趋势的场景中。 @AmanGarg 来自问题:“但理想情况下,如果它已经存在,它不应该尝试保存新角色。下面我添加我所有的表和保存方法。”表示在角色不存在时他会保存它。我同意他不应该保留这个新角色。关于处理多行,我发布了一个更新,但在这个阶段编码的人肯定可以确定他想如何修改代码。这不是适用于所有情况的即插即用解决方案。 然后需要添加验证。 Cascade.ALL 在这里无效。 感谢您的反馈。如前所述,您可能不想保存尚未在数据库中的角色,尽管您的问题看起来像是您正在尝试做的事情。与不使用级联ALL相反,我认为这取决于。在这种情况下使用 ALL 可以说并不理想,但它再次符合您的问题。比如说,如果你有不同的情况,你想在一个用户下保存 cmets,你可能想要级联用户操作来添加/删除 cmets。我会说采取更好的方法并尝试推荐的步骤,除非你真的必须这样做! @VatsalRahul

以上是关于在 Spring Security 中添加新用户时出现异常的主要内容,如果未能解决你的问题,请参考以下文章

在 Spring Security 中添加新用户

如何在不重启 Spring Boot 的情况下使用 Spring Security 添加新用户

使用 Spring Security 在登录时添加employeeId

Spring security logout - 仅在登录用户触发注销时添加消息

在 Spring Boot 库的上下文中添加新的 spring-security 链过滤器的预期方法

:REST spring security - 手动验证新用户并获取访问令牌