0

I'm trying to make my own custom login page for Spring boot. I just get "http://localhost:8080/login?error" and that's it. Can't debug anything in the console. No information at all. What am I doing wrong? I'm new to Spring Boot and Java.

login.html

<div id="login-form-wrapper">
<form id="login-form" action="/login"  method="post">
    <div class="form-group">
        <label for="username">Email address</label>
        <input type="email" name="username" id="username" class="form-control"
               aria-describedby="emailHelp" autofocus required/>
        <small id="emailHelp" class="form-text text-muted">We'll never share your email with anyone else.</small>
    </div>
    <div class="form-group">
        <label for="password">Password</label>
        <input type="password" name="password" class="form-control" id="password" required/>
    </div>
    <button type="submit" class="btn btn-primary">Login</button>
    <div id="no-account-wrapper">
        <small>Don't have an account?<a href="/registration"> Sign up</a></small>
    </div>
</form>

Controller

@RequestMapping(value = "/login", method = RequestMethod.GET)
public String showLoginForm() {
    return "login";
}

SecurityConfig

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    final
    UserDetailsService userDetailsService;

    @Autowired
    public SecurityConfig(@Qualifier("myUserDetailsService") UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    @Bean
    public DaoAuthenticationProvider authProvider() {
        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
        authProvider.setUserDetailsService(userDetailsService);
        authProvider.setPasswordEncoder(bCryptPasswordEncoder());
        return authProvider;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(authProvider());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.authorizeRequests().antMatchers("/resources/**", "/css/**", "/registration", "/").permitAll()
                .anyRequest().authenticated()
        .and().formLogin()
        .loginPage("/login")
                .usernameParameter("email")
                .passwordParameter("password")
        .successForwardUrl("/profile")
        .permitAll();

    }

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

I'm not quite sure with the annotations @Configuration and @ComponentScan

EDIT:

Added registration.html and controller for the registration. Removed @ComponentScan from SecurityConfig

registration.html

<div id="registration-form-wrapper">
<form id="registration-form" th:action="@{'/perform_registration'}" th:object="${user}" method="post">
    <div class="form-group">
        <label for="first-name">First Name</label>
        <input type="text" class="form-control" th:field="*{firstName}" id="first-name" required autofocus/>
    </div>
    <div class="form-group">
        <label for="last-name">Last Name</label>
        <input type="text" class="form-control" th:field="*{lastName}" id="last-name" required/>
    </div>
    <div class="form-group">
        <label for="email">Email address</label>
        <input type="email" class="form-control" id="email" th:field="*{email}" aria-describedby="emailHelp"
               required/>
        <small id="emailHelp" class="form-text text-muted">We'll never share your email with anyone else.</small>
        <p class="alert alert-danger" th:if="${#fields.hasErrors('email')}" th:errors="*{email}"></p>
    </div>
    <div class="form-group">
        <label for="password">Password</label>
        <input type="password" class="form-control" th:field="*{password}" id="password" minlength="8" required/>
    </div>
    <div class="form-group">
        <label for="passwordRepeat">Repeat password</label>
        <input type="password" class="form-control" th:field="*{matchingPassword}" id="passwordRepeat" minlength="8"
               required/>
    </div>
    <button type="submit" class="btn btn-primary">Sign up</button>
    <div id="no-account-wrapper">
        <small>Already have an account?<a href="login.html" th:href="@{'/'}"> Login</a></small>
    </div>
</form>

Controller for registration

@RequestMapping(value = "/registration", method = RequestMethod.GET)
public String showRegistrationForm(WebRequest request, Model model) {
    UserDto userDto = new UserDto();
    model.addAttribute("user", userDto);
    return "registration";
}

@PostMapping(value = "/perform_registration", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public String registerUserAccount(@ModelAttribute(name = "user") @Valid UserDto userDto, BindingResult result) {

    User existing = userRepository.findByEmail(userDto.getEmail());
    if (existing != null) {
        log.info("User with email {} already exists -> {}", userDto.getEmail(), existing);
        result.rejectValue("email", "required", "User with email " + userDto.getEmail() + " already exists.");
    }

    if (result.hasErrors()) {
        return "registration";
    }

    User newUser = new User();
    newUser.setFirstName(userDto.getFirstName());
    newUser.setLastName(userDto.getLastName());
    newUser.setEmail(userDto.getEmail());
    newUser.setPassword(getPasswordEncoder.encode(userDto.getPassword()));
    userRepository.save(newUser);

    return "redirect:/login";
}

EDIT 2:

@Service
public class MyUserDetailsService implements UserDetailsService {

    final UserRepository userRepository;

    public MyUserDetailsService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
        Optional<User> user = Optional.ofNullable(userRepository.findByEmail(email));
        user.orElseThrow(()-> new UsernameNotFoundException("Not found: " + email));

        return user.map(MyUserDetails::new).get();
    }
}

Thanks in advance!

svrdoljak
  • 148
  • 1
  • 2
  • 10

1 Answers1

1

@Configuration "indicates that the class has @Bean definition methods. So Spring container can process the class and generate Spring Beans to be used in the application." See more here.

It is needed for your Security configuration class since it has some beans. You don't need @ComponentScan.

Second following this explanation of @Bean and @Autowired. You need to create your custom UserDetailsService which must have been implemented by your user entity (for authentication of users) as a bean and injected into your DAO authentication.

@Bean
@Override
public UserDetailsService userDetailsService() {

    return new CustomCustomerDetailsService();   
}



@Bean
public DaoAuthenticationProvider authenticationProvider() {

    DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
    authProvider.setUserDetailsService(userDetailsService());
    authProvider.setPasswordEncoder(encoder());   
    return authProvider;    
}

See this answer to know how to implement a custom user details class.

Complete Custom UserDetailsService

@Service("customCustomerDetailsService")
public class CustomCustomerDetailsService implements UserDetailsService  {

    @Autowired
    private CustomerRepository customers;    




    @Override
    public UserDetails loadUserByUsername(String email)  {

      return this.customers.findByEmail(email)
            .orElseThrow(() -> new UsernameNotFoundException("Username: " + email + " not found"));



    }

}
tksilicon
  • 3,276
  • 3
  • 24
  • 36
  • In EDIT 2 i have my UserDetailsService. Can you check this out? – svrdoljak Dec 16 '19 at 09:05
  • You can simplify that thus: @Override public UserDetails loadUserByUsername(String email) { return this.userRepository.findByEmail(email) .orElseThrow(() -> new UsernameNotFoundException("Username: " + email + " not found")); } – tksilicon Dec 16 '19 at 09:12
  • Your User entity should implement UserDetails. – tksilicon Dec 16 '19 at 09:13
  • I did implement UserDetails. But when i try to implement your Override method above. I get a red warning on ".orElseThrow". I did this: @Override public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException{ return userRepository.findByEmail(email).orElseThrow(() -> new UsernameNotFoundException("Username: " + email + " not found")); } – svrdoljak Dec 16 '19 at 09:18
  • I found the issue. I was sending the whole time null values because I didn't change my constructor to getEmail() and getPassword() in MyUserDetails. Such a dumb misstake. Thank you for your answer! I have a clearer picture of how all of this works. – svrdoljak Dec 16 '19 at 13:22