После отправки запроса на аутентификацию в 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>