如何使用Spring“匹配”方法检查旧密码?

问题描述 投票:2回答:3
@Autowired
private PasswordEncoder passwordEncoder;

@Autowired
private OldPasswordsService oldPasswordsService;

Optional<OldPasswords> list = oldPasswordsService.findEncryptedPassword(passwordEncoder.encode("new password entered form web reset form"));
            OldPasswords value = list.get();
            boolean matches = passwordEncoder.matches("new password entered form web reset form", value.getEncryptedPassword());

            if (matches)
            {
                return new ResponseEntity<>("PASSWORD_ALREADY_USED", HttpStatus.BAD_REQUEST);
            }
            else
            {
                OldPasswords oldPasswords = new OldPasswords();
                oldPasswords.setEncryptedPassword(passwordEncoder.encode(resetDTO.getPassword()));
                oldPasswordsService.save(oldPasswords);
            }

旧密码表:

@Table(name = "old_passwords")
public class OldPasswords implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", unique = true, updatable = false, nullable = false)
    private int id;

    @Column(name = "encrypted_password", length = 255)
    private String encryptedPassword;

    @Column(name = "password_owner_id", length = 4)
    private Integer passwordOwnerId;

    @Column(name = "created_at")
    @Convert(converter = LocalDateTimeConverter.class)
    private LocalDateTime createdAt;

但是我得到了java.util.NoSuchElementException: No value present。您知道我如何实现比较旧密码和新密码的逻辑吗?

编辑:我尝试过这种设计:

SQL查询:

public List<OldPasswords> findByOwnerId(Integer ownerId) {
        String hql = "select e from " + OldPasswords.class.getName() + " e where e.passwordOwnerId = :passwordOwnerId ORDER BY e.createdAt DESC";
        TypedQuery<OldPasswords> query = entityManager.createQuery(hql, OldPasswords.class).setMaxResults(3).setParameter("passwordOwnerId", ownerId);
        List<OldPasswords> list = query.getResultList();
        return list;
    }

端点:

@PostMapping("reset_password")
  public ResponseEntity<?> reset(@RequestBody PasswordResetDTO resetDTO) {
    return this.userService.findByLogin(resetDTO.getName()).map(user -> {

        Integer userId = user.getId();

        List<OldPasswords> list = oldPasswordsService.findByOwnerId(userId);

        if(!list.isEmpty() && !list.isEmpty()) {

            for (int i = 0; i<list.size(); i++){
                OldPasswords value = list.get(i);

                boolean matches = passwordEncoder.matches(resetDTO.getPassword(), value.getEncryptedPassword());
                if (matches) {
                    return new ResponseEntity<>("PASSWORD_ALREADY_USED", HttpStatus.BAD_REQUEST);
                }
            }
        }

        OldPasswords oldPasswords = new OldPasswords();
        oldPasswords.setEncryptedPassword(passwordEncoder.encode(resetDTO.getPassword()));
        oldPasswords.setPasswordOwnerId(userId);
        oldPasswordsService.save(oldPasswords);

        user.setEncryptedPassword(passwordEncoder.encode(resetDTO.getPassword()));

        user.setResetPasswordToken(null);
        userService.save(user);
        return ok().build();
    }).orElseGet(() -> notFound().build());
}

但是当我使用相同的密码多次更改代码时,不会显示错误PASSWORD_ALREADY_USED

java spring spring-boot spring-security spring-security-oauth2
3个回答
0
投票

检查可选类型列表是否包含有效的必需值

Optional<OldPasswords> list = oldPasswordsService.findEncryptedPassword(passwordEncoder.encode("new password entered form web reset form"));
OldPasswords value = list.orElse(null);

if(value != null) {
    boolean matches = passwordEncoder.matches("new password entered form web reset form", value.getEncryptedPassword());
    if (matches) {
        return new ResponseEntity<>("PASSWORD_ALREADY_USED", HttpStatus.BAD_REQUEST);
    }

    OldPasswords oldPasswords = new OldPasswords();
    oldPasswords.setEncryptedPassword(passwordEncoder.encode(resetDTO.getPassword()));
    oldPasswordsService.save(oldPasswords);
    return new ResponseEntity<>("New Password Saved" , HttpStatus.OK);
}
return new ResponseEntity<>("Incorrect Password Provided" , HttpStatus.BAD_REQUEST);

或类似@Manuel的建议

Optional<OldPasswords> list = oldPasswordsService.findEncryptedPassword(passwordEncoder.encode("new password entered form web reset form"));

if(list.isPresent()) {
    OldPasswords value = list.get();
    boolean matches = passwordEncoder.matches("new password entered form web reset form", value.getEncryptedPassword());
    if (matches) {
        return new ResponseEntity<>("PASSWORD_ALREADY_USED", HttpStatus.BAD_REQUEST);
    }

    OldPasswords oldPasswords = new OldPasswords();
    oldPasswords.setEncryptedPassword(passwordEncoder.encode(resetDTO.getPassword()));
    oldPasswordsService.save(oldPasswords);
    return new ResponseEntity<>("New Password Saved" , HttpStatus.OK);
}
return new ResponseEntity<>("Incorrect Password Provided" , HttpStatus.BAD_REQUEST);

0
投票

我已经使用简单的解决方案解决了这个问题。

    BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(12);
    if (encoder.matches(newPassword, oldPassword)) 
    {
          System.out.println("Successfully logged in!");

    }
      else 
           {
                System.out.println("Incorrect Password.");
            }

newPasswordoldPassword必须在String中,以便它与两个密码都匹配。


0
投票

我认为您的代码有几个问题。

1。 'passwordEncoder'的类型

根据实际使用的编码算法,有不同类型的密码编码器。如果“ passwordEncoder”的类型为MD5,SHA1,则您很可能会遇到密码冲突,因为您希望密码是唯一的。

表示您的方法是,如果一个用户的密码较弱,例如“ topSecret123”,而另一个用户的密码相同“ topSecret123”,则您的方法

oldPasswordsService.findEncryptedPassword(...)

将返回多个条目,而不是一个。

这将导致例如NonUniqueResultException或其他内容。

1.1可能的解决方案:

将密码与用户名相关联。提取由userId(或类似名称)指定的用户,并使用该用户的密码进行密码检查。

1.2另一种可能的解决方案

使用例如BCryptPasswordEncoder。这种类型的PasswordEncoder会在您的酒中加盐。这避免了数据库中可能存在重复的条目。这些类型的密码编码器无法计算密码,也无法检查密码是否匹配(如果仅提供“密码”)。由于它们使用带有编码密码的“盐”,因此这些类型的密码编码器需要使用(盐+哈希)密码作为输入,以便检查所提供的密码是否匹配。

2。实际问题

代码

OldPasswords value = list.get();

是问题。 Optional<OldPasswords>可以包含null值。在.get()上调用Optional将产生null值,从而产生java.util.NoSuchElementException: No value present

Optional<OldPasswords> list = oldPasswordsService.findEncryptedPassword(passwordEncoder.encode("new password entered form web reset form"));

if (!list.isPresent()) {
 return new ResponseEntity<>("The old password value you've entered is incorrect", HttpStatus.UNAUTHORIZED);
}

OldPasswords value = list.get();
boolean matches = passwordEncoder.matches("new password entered form web reset form", value.getEncryptedPassword());

if (matches)
{
    return new ResponseEntity<>("PASSWORD_ALREADY_USED", HttpStatus.BAD_REQUEST);
}
else
{
    OldPasswords oldPasswords = new OldPasswords();
    oldPasswords.setEncryptedPassword(passwordEncoder.encode(resetDTO.getPassword()));
    oldPasswordsService.save(oldPasswords);
}

3。 OldPasswords实体

您既不必将@Id列设为unique=true,也不必将nullable=false设为updateable=false

4。混合层

您发布的代码使用服务并更新域对象。它确实返回ResponseEntity。您显然将应用程序的不同层混合在一起。

5。公开信息

您公开了所选择的(新)密码已被其他用户使用的信息!不要那样做!由于第1点,这加起来。我已经列出。

编辑:

更新问题后,我也想更新我的答案。由于更新后的问题中的代码片段无法编译,因此我想根据我从代码片段中了解的内容,制作一个非常简单的基本示例。

我不评论问题中显示的“重置密码”设计的概念,因为中间缺少很多代码。

包括测试的整个代码可以在这里找到:https://github.com/mschallar/so_oldpasswords_example

问题中要求的功能代码是:

@PostMapping("reset_password")
public ResponseEntity<?> reset(@RequestBody PasswordResetDTO resetDTO) {

    Optional<User> findByLogin = this.userService.findByLogin(resetDTO.getName());

    if (!findByLogin.isPresent()) {
        return ResponseEntity.notFound().build();
    }

    User user = findByLogin.get();
    Integer userId = user.getUserId();

    String encodedPassword = passwordEncoder.encode(resetDTO.getPassword());

    for (OldPasswords oldPasswords : oldPasswordsService.findByOwnerId(userId)) {

        if (passwordEncoder.matches(resetDTO.getPassword(), oldPasswords.getEncryptedPassword())) {
            // Information: Don't do that! Don't reveal that another user already has such a password!
            log.info("Password already used.");
            return new ResponseEntity<>("PASSWORD_ALREADY_USED", HttpStatus.BAD_REQUEST);
        }

    }

    OldPasswords oldPasswords = new OldPasswords();
    oldPasswords.setEncryptedPassword(passwordEncoder.encode(encodedPassword));
    oldPasswords.setPasswordOwnerId(userId);
    oldPasswordsService.save(oldPasswords);

    user.setEncryptedPassword(encodedPassword);

    user.setResetPasswordToken(null);
    userService.save(user);

    return ResponseEntity.ok().build();

}

-1
投票

您将未编码的新密码与旧密码进行比较:

© www.soinside.com 2019 - 2024. All rights reserved.