I faced a terrible bug in my login/registration program.
The main idea is to register, press submit and all the data had to be stored in postgres database, and after that user should login(all project code provided below) to his profile. But when I try to register and submit all data, the password field become empty, however other fields is normally filled, and of course when I tried to login with email and password I'm getting error message(Invalid email or password which I created for such situations).
I think the issue maybe with password encoding, but I don't have idea how to fix this.
Security config
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/css/**").permitAll()
.antMatchers("/js/**").permitAll()
.antMatchers("/img/**").permitAll()
.antMatchers("/registration/**").permitAll()
.antMatchers("/").permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/login").permitAll()
.loginProcessingUrl("/login")
.defaultSuccessUrl("/profile")
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/")
.and()
.httpBasic();
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Authorization controller
@AllArgsConstructor
@Controller
public class AuthController {
private final EmployeeService employeeService;
@GetMapping("/")
public String getHomePage() {
return "home";
}
@GetMapping("/login")
public String getLoginPage() {
return "login";
}
@GetMapping("/profile")
public String getSuccessPage() {
return "profile";
}
@GetMapping("registration")
public String getRegisterPage(Model model) {
EmployeeDto employee = new EmployeeDto();
model.addAttribute("user", employee);
return "registration";
}
@PostMapping("/registration/save")
public String postRegisterPage(@Valid @ModelAttribute("user") EmployeeDto employee,
BindingResult result,
Model model){
Optional<Employee> existing = employeeService.findByEmail(employee.getEmail());
if (existing != null) {
result.rejectValue("email", null, "There is already an account registered with that email");
}
if (result.hasErrors()) {
model.addAttribute("user", employee);
return "registration";
}
employeeService.saveAndFlush(employee);
return "redirect:/registration?success";
}
}
Employee Dto
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class EmployeeDto
{
private Long id;
@NotEmpty
private String firstName;
@NotEmpty
private String lastName;
@NotEmpty(message = "Email should not be empty")
@Email
private String email;
@NotEmpty(message = "Password should not be empty")
private String password;
@NotEmpty(message = "Department should not be empty")
private String department;
@NotEmpty(message = "Birth date should not be empty")
private String birthDate;
@NotEmpty(message = "Phone number should not be empty")
private String phoneNumber;
}
Entities
@Getter
@Setter
@ToString
@Entity
@Table(name = "employees")
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "first_name", nullable = false)
private String firstName;
@Column(name = "last_name", nullable = false)
private String lastName;
@Column(name = "department", nullable = false)
private String department;
@Column(name = "birth_date", nullable = false)
private String birthDate;
@Column(name = "phone_number", nullable = false)
private String phoneNumber;
@Column(name = "email", nullable = false, unique = true)
private String email;
@Column(name = "password", nullable = false)
private String password;
@ManyToMany(fetch = FetchType.EAGER, cascade=CascadeType.ALL)
@JoinTable(
name="employees_roles",
joinColumns={@JoinColumn(name="EMPLOYEE_ID", referencedColumnName="ID")},
inverseJoinColumns={@JoinColumn(name="ROLE_ID", referencedColumnName="ID")})
private List<Role> roles = new ArrayList<>();
}
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name="roles")
public class Role
{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable=false, unique=true)
private String name;
@ManyToMany(mappedBy="roles")
private List<Employee> employee;
}
Employee and role repositories
@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long>{
Optional<Employee> findByEmail(String email);
}
@Repository
public interface RoleRepository extends JpaRepository<Role, Long> {
Role findByName(String name);
}
Employee Service
public interface EmployeeService {
void saveAndFlush(EmployeeDto employeeDto);
Optional<Employee> findByEmail(String email);
List<EmployeeDto> findAll();
}
Employee Service Implementation
@Service
public class EmployeeServiceImpl implements EmployeeService {
@Autowired
private EmployeeRepository employeeRepository;
private RoleRepository roleRepository;
private PasswordEncoder passwordEncoder;
@Override
public void saveAndFlush(EmployeeDto employeeDto) {
Employee employee = new Employee();
employee.setEmail(employeeDto.getEmail());
employee.setPassword(passwordEncoder.encode(employeeDto.getPassword()));
Role role = roleRepository.findByName("ROLE_ADMIN");
if(role == null){
role = checkRoleExist();
}
employee.setRoles(Arrays.asList(role));
employeeRepository.save(employee);
}
@Override
public Optional<Employee> findByEmail(String email) {
return employeeRepository.findByEmail(email);
}
@Override
public List<EmployeeDto> findAll() {
List<Employee> employees = employeeRepository.findAll();
return employees.stream().map((employee) -> convertEntityToDto(employee))
.collect(Collectors.toList());
}
private EmployeeDto convertEntityToDto(Employee employee){
EmployeeDto employeeDto = new EmployeeDto();
employeeDto.setEmail(employee.getEmail());
return employeeDto;
}
private Role checkRoleExist() {
Role role = new Role();
role.setName("ROLE_ADMIN");
return roleRepository.save(role);
}
}
Employee Details Service Implementation
@Service
public class EmployeeDetailsServiceImpl implements UserDetailsService {
private EmployeeRepository employeeRepository;
public EmployeeDetailsServiceImpl(EmployeeRepository employeeRepository) {
this.employeeRepository = employeeRepository;
}
@SuppressWarnings("unchecked")
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
Employee employee = employeeRepository.findByEmail(email).orElseThrow(() ->
new UsernameNotFoundException("User doesn't exists"));
return new org.springframework.security.core.userdetails.User(
employee.getEmail(), employee.getPassword(), (Collection<? extends GrantedAuthority>) Set.of(employee.getRoles())
);
}
}
HTML login and register forms
<body>
<div >
<div >
<div >
<div >
<div >Вхід</div>
<div th:if="${param.error}">
<div >Invalid Email or Password</div>
</div>
<form method="post" role="form" th:action="@{/login}">
<div >
<div >
<i ></i>
<input type="text" placeholder="Введіть пошту" id="email" name="email" required/>
</div>
<div >
<i ></i>
<input type="password" placeholder="Введіть пароль" id="password" name="password" required/>
</div>
<div >
<input type="submit" value="Вхід">
</div>
<div >
<label>
<a href="@{/registration}">Зареєструватися</a>
</label>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</body>
</html>
Registration form
<body>
<div >
<div >
<div >
<div >
<div >Реєстрація</div>
<div th:if="${param.success}">
<div >You've successfully registered
to our app!</div>
</div>
<form method="post" role="form" th:action="@{/registration/save}" th:object="${user}">
<div >
<div >
<i ></i>
<input type="text" placeholder="Введіть ім'я" id="firstName" name="firstName" th:field="*{firstName}"/>
<p th:errors="*{firstName}"
th:if="${#fields.hasErrors('firstName')}">
</p>
</div>
<div >
<i ></i>
<input type="text" placeholder="Введіть прізвище" id="lastName" name="lastName" th:field="*{lastName}"/>
<p th:errors="*{lastName}"
th:if="${#fields.hasErrors('lastName')}">
</p>
</div>
<div >
<i ></i>
<input type="text" placeholder="Введіть пошту" id="email" name="email" th:field="*{email}"/>
<!-- <p th:errors="*{email}"
th:if="${#fields.hasErrors('email')}">
</p>
-->
</div>
<div >
<i ></i>
<input type="password" placeholder="Введіть пароль" id="password" name="password" th:field="*{password}"/>
<p th:errors="*{password}"
th:if="${#fields.hasErrors('password')}">
</p>
</div>
<div >
<i ></i>
<input type="text" placeholder="Введіть дату народження" id="birthDate" name="birthDate" th:field="*{birthDate}"/>
<p th:errors="*{birthDate}"
th:if="${#fields.hasErrors('birthDate')}">
</p>
</div>
<div >
<i ></i>
<input type="text" placeholder="Введіть підрозділ" id="department" name="department" th:field="*{department}"/>
<p th:errors="*{department}"
th:if="${#fields.hasErrors('department')}">
</p>
</div>
<div >
<i ></i>
<input type="text" placeholder="Введіть номер телефону" id="phoneNumber" name="phoneNumber" th:field="*{phoneNumber}"/>
<p th:errors="*{phoneNumber}"
th:if="${#fields.hasErrors('phoneNumber')}">
</p>
</div>
<div >
<input type="submit" value="Зареєструватися">
</div>
<div ><label><a th:href="@{/login}">Увійти</a></label></div>
</div>
</form>
</div>
</div>
</div>
</div>
</body>
</html>
CodePudding user response:
Where is the UserDetail
implementation?
Make sure your
EmployeeDto
is correct. Also u have used one@Autowired
for three objects in yourEmployeeServiceImplementation
. I am not sure that works. Try using@Autowired
for each objects.Its better to Autowire the
employeeRepository
object rather than using constructor in yourEmployeeDetailsServiceImpl
.I'm still figuring out why your only password is getting null in table. I guess since only it is being passed through the passwordEncoder so might be something to do with that.
Hope this helps