После отправки запроса на аутентификацию в Spring Security, возвращается та же форма в связи с некорректными данными , хотя данные корректны

Аутентификация происходит по кастомным полям. Но тип одного из полей (pin) соответствует типу поля, который требует UserDetails, а другое поле (phoneNumber) имеет тип Long, а не String как юзернейм, и используется в качестве Principal.

После ввода корректных данных в поля и отправки запроса на аутентификацию, Spring просто заново отправляет форму для аутентификации и выбрасывает ошибку "Неверный номер телефона или PIN-код". У меня есть подозрение, что он просто не получает Principal, но я в этом не уверен и не знаю, почему так.

В чем может быть ошибка, что я сделал неправильно и как ее исправить?

Ниже будут показаны коды, которые могут понадобиться при решении проблемы:

Конфигурационный файл Spring Security:

@Configuration
@EnableWebSecurity
public class SecurityConfiguration {
    private final AuthProviderImpl authProvider;

    @Autowired
    public SecurityConfiguration(@Lazy AuthProviderImpl authProvider) {
        this.authProvider = authProvider;
    }

    @Bean
    public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
        AuthenticationManagerBuilder authenticationManagerBuilder =
                http.getSharedObject(AuthenticationManagerBuilder.class);
        authenticationManagerBuilder.authenticationProvider(authProvider);

        return authenticationManagerBuilder.build();
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                .requestMatchers("/BankAccounts/new").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/auth/login")
                .loginProcessingUrl("/auth/login")
                .permitAll();
        return http.build();
    }

    @Bean
    public PasswordEncoder getPasswordEncoder() {
        return new BCryptPasswordEncoder(); 
    }

    @Bean
    public AuthProviderImpl authProvider(BankAccountsDetailService bankAccountsDetailService) {
        return new AuthProviderImpl(bankAccountsDetailService, getPasswordEncoder());
    }
}

Java класс, реализующий интерфейс UserDetails:

public class BankAccountDetails implements UserDetails {

    private final BankAccount bankAccount;
    private final Long phoneNumber;

    public BankAccountDetails(BankAccount bankAccount) {
        this.bankAccount = bankAccount;
        this.phoneNumber = bankAccount.getPhoneNumber();
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return List.of(new SimpleGrantedAuthority("ROLE_USER")); // Пример роли
    }

    @Override
    public String getPassword() {
        return this.bankAccount.getPin();
    }

    @Override
    public String getUsername() {
        return this.bankAccount.getFullName();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true; // Логика проверки может быть добавлена позже
    }

    @Override
    public boolean isAccountNonLocked() {
        return true; // Логика проверки может быть добавлена позже
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true; // Логика проверки может быть добавлена позже
    }

    @Override
    public boolean isEnabled() {
        return true; // Логика проверки может быть добавлена позже
    }

    public Long getPhoneNumber() {
        return phoneNumber;
    }

    public BankAccount getBankAccount() {
        return this.bankAccount;
    }
}

Кастомный провайдер аутентификации:

@Component
public class AuthProviderImpl implements AuthenticationProvider {

    private final BankAccountsDetailService bankAccountsDetailService;
    private final PasswordEncoder passwordEncoder;

    private static final Logger logger = LoggerFactory.getLogger(AuthProviderImpl.class);

    @Autowired
    public AuthProviderImpl(BankAccountsDetailService bankAccountsDetailService, PasswordEncoder passwordEncoder) {
        this.bankAccountsDetailService = bankAccountsDetailService;
        this.passwordEncoder = passwordEncoder;
    }

    @Override
    public PhoneNumberAuthToken authenticate(Authentication authentication) throws AuthenticationException {
        logger.info("authenticate method is called");

        Object principal = authentication.getPrincipal();
        if (principal == null) {
            logger.error("Principal is null");
            throw new BadCredentialsException("Principal is null");
        }

        String phoneNumberStr = principal.toString();
        logger.info("Received phone number: {}", phoneNumberStr);

        try {
            Long phoneNumber = Long.parseLong(phoneNumberStr);
            String pin = authentication.getCredentials().toString();
            logger.info("Phone number: {}", phoneNumber);
            logger.info("PIN: {}", pin);

            // Загрузите детали банковского аккаунта по номеру телефона
            BankAccountDetails bankAccountDetails = (BankAccountDetails) bankAccountsDetailService.loadUserByPhoneNumber(phoneNumber);
            if (bankAccountDetails == null) {
                logger.error("Bank account not found for phone number: {}", phoneNumber);
                throw new BadCredentialsException("Неверный номер телефона или PIN-код");
            }

            // Проверка PIN-кода с использованием PasswordEncoder
            if (!passwordEncoder.matches(pin, bankAccountDetails.getPassword())) {
                logger.error("PIN does not match for phone number: {}", phoneNumber);
                throw new BadCredentialsException("Неверный номер телефона или PIN-код");
            }

            logger.info("Authentication successful for phone number: {}", phoneNumber);
            return new PhoneNumberAuthToken(bankAccountDetails.getAuthorities(), phoneNumber, pin);

        } catch (NumberFormatException e) {
            logger.error("Неверный формат номера телефона: {}", phoneNumberStr, e);
            throw new BadCredentialsException("Неверный формат номера телефона");
        } catch (UsernameNotFoundException e) {
            logger.error("User not found for phone number: {}", phoneNumberStr, e);
            throw new BadCredentialsException("Неверный номер телефона или PIN-код");
        } catch (Exception e) {
            logger.error("Ошибка при аутентификации", e);
            throw new BadCredentialsException("Ошибка при аутентификации");
        }
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return PhoneNumberAuthToken.class.isAssignableFrom(authentication);
    }
}

Токен для аутентификации:

public class PhoneNumberAuthToken extends AbstractAuthenticationToken {

    private final Long phoneNumber;

    private final String pin;

    public PhoneNumberAuthToken(Collection<? extends GrantedAuthority> authorities, Long phoneNumber, String pin) {
        super(authorities);
        this.phoneNumber = phoneNumber;
        this.pin = pin;
        setAuthenticated(false);
    }

    @Override
    public Object getCredentials() {
        return pin;
    }

    @Override
    public Object getPrincipal() {
        return phoneNumber;
    }
}

Тело формы ( если проблема может возникать на моменте отправки запроса ):

<body>
<div class="login-container">
    <h2 class="mt-5">Форма Входа</h2>
    <form action="#" th:action="@{/auth/login}" method="post" class="mt-4">
        <div class="mb-3">
            <label for="phoneNumber" class="form-label">Номер телефона</label>
            <input type="text" class="form-control" id="phoneNumber" name="phoneNumber" required>
        </div>
        <div class="mb-3">
            <label for="pin" class="form-label">PIN-код</label>
            <input type="password" class="form-control" id="pin" name="pin" required>
        </div>
        <button type="submit" class="btn btn-primary">Войти</button>
    </form>

    <!-- Отображение ошибок аутентификации -->
    <div th:if="${param.error}" class="alert alert-danger mt-3">
        Неверный номер телефона или PIN-код.
    </div>
</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.3/js/bootstrap.bundle.min.js"></script>
</body>

Ответы (0 шт):