Как проверить старые пароли с помощью метода Spring «match»? - PullRequest
2 голосов
/ 11 марта 2020
@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. Знаете ли вы, как я могу реализовать logi c, который сравнивает старый и новый пароли?

РЕДАКТИРОВАТЬ: я пробовал этот дизайн:

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 не отображается.

Ответы [ 3 ]

1 голос
/ 14 марта 2020

Я думаю, у вашего кода есть несколько проблем.

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. Я перечислил.

Редактировать:

После того, как вопрос был обновлен, я также хочу обновить свой ответ. Поскольку код, вставленный в обновленный вопрос, не компилируется, я хотел сделать очень простой, базовый c пример, основанный на том, что я знаю из фрагментов кода.

Я не комментирую концепцию Дизайн «сброса пароля», как показано в вопросе, так как в промежутке между ними отсутствует много кода.

Весь код, включая тесты, можно найти здесь: 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();

}
0 голосов
/ 15 марта 2020

Я решил это, используя простое решение.

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

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

newPassword и oldPassword должны быть в Строка , чтобы он совпадал с обоими паролями.

0 голосов
/ 14 марта 2020

проверьте, что если список необязательного типа содержит действительное обязательное значение

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);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...