Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

My Spring-Boot custom login form isn't working [UPDATED]

I'm new to Spring-Boot and currently, I'm developing a custom login form with a MySQL database connection.

So I have already developed the registration function and it works fine.

But when I try to log in to an account, it always showing "Invalid username and password."

I'm using Eclipse IDE.

Below is the Controller class: WebMvcConfiguration.java

@ComponentScan("org.springframework.security.samples.mvc")
@Controller
@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer  {

    

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/login").setViewName("login");
        registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
    }

     @GetMapping("/login")
    public String login() {
      return "login";
    }
    
    @PostMapping("/customerAccount")
    public String authenticate() {
      // authentication logic here
      return "customerAccount";
    }
    
    @GetMapping("/adminDashboard")
    public String adminDashboard() {
        return "adminDashboard";
    }
    
    @GetMapping("/Category")
   public String Category() {
     return "Category";
   }
    @GetMapping("/Index")
   public String Index() {
     return "Index";
   }
    
    @PostMapping("/RatingAccount")
    public String RatingAccount() {
      return "RatingAccount";
    }
    
    public void addResourceHandlers(final ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/static/**").addResourceLocations("/resources/static/");
    } 
    
}


Below is the UserAccountController.java

@RestController
@Controller
public class UserAccountController {

    @Autowired
    private CustomerRepository userRepository;

    @Autowired
    private ConfirmationTokenRepository confirmationTokenRepository;

    @Autowired
    private EmailSenderService emailSenderService;

    @RequestMapping(value="/register", method = RequestMethod.GET)
    public ModelAndView displayRegistration(ModelAndView modelAndView, Customer user)
    {
        modelAndView.addObject("user", user);
        modelAndView.setViewName("register");
        return modelAndView;
    }

    @RequestMapping(value="/register", method = RequestMethod.POST)
    public ModelAndView registerUser(ModelAndView modelAndView, Customer user)
    {

        Customer existingUser = userRepository.findByEmailIdIgnoreCase(user.getEmailId());
        if(existingUser != null)
        {
            modelAndView.addObject("message","This email already exists!");
            modelAndView.setViewName("error"); 
        }
        else
        {
            userRepository.save(user);

            ConfirmationToken confirmationToken = new ConfirmationToken(user);

            confirmationTokenRepository.save(confirmationToken);

            SimpleMailMessage mailMessage = new SimpleMailMessage();
            mailMessage.setTo(user.getEmailId());
            mailMessage.setSubject("Complete Registration!");
            mailMessage.setFrom("[email protected]");
            mailMessage.setText("To confirm your account, please click here : "
            +"http://localhost:8082/confirm-account?token="+confirmationToken.getConfirmationToken());

            emailSenderService.sendEmail(mailMessage);

            modelAndView.addObject("emailId", user.getEmailId());

            modelAndView.setViewName("successfulRegisteration");
        }

        return modelAndView;
    }

    @RequestMapping(value="/confirm-account", method= {RequestMethod.GET, RequestMethod.POST})
    public ModelAndView confirmUserAccount(ModelAndView modelAndView, @RequestParam("token")String confirmationToken)
    {
        ConfirmationToken token = confirmationTokenRepository.findByConfirmationToken(confirmationToken);

        if(token != null)
        {
            Customer user = token.getCustomer();
            //Customer user = userRepository.findByEmailIdIgnoreCase(token.getCustomer().getEmailId());
            user.setEnabled(true);
            userRepository.save(user);
            modelAndView.setViewName("accountVerified");
        }
        else
        {
            modelAndView.addObject("message","The link is invalid or broken!");
            modelAndView.setViewName("error");
        }

        return modelAndView;
    }
    
    @RequestMapping(value="/login", method= {RequestMethod.GET, RequestMethod.POST})
   // @ResponseBody
    public ModelAndView login(ModelAndView modelAndView, @RequestParam("emailID")String email, @RequestParam("password")String password)
    {
        Customer user = userRepository.findByEmailIdIgnoreCase(email);
        
        if(user == null) {
            modelAndView.addObject("message1","Invalid E-mail. Please try again.");
            modelAndView.setViewName("login");
        }
        else if (user != null && user.getPassword()!=password) {
            modelAndView.addObject("message1","Incorrect password. Please try again.");
            modelAndView.setViewName("login");
        }
        else if (user != null && user.getPassword()==password && user.isEnabled()==false) {
            modelAndView.addObject("message1","E-mail is not verified. Check your inbox for the e=mail with a verification link.");
            modelAndView.setViewName("login");
        }
        else if (user != null && user.getPassword()==password && user.isEnabled()==true) { 
            modelAndView.addObject("message1","Welcome! You are logged in.");
            modelAndView.setViewName("customerAccount");
        }
        return modelAndView;
    }
    

    @RequestMapping(value="/customerDetails", method = RequestMethod.GET)
    public ModelAndView displayCustomerList(ModelAndView modelAndView)
    {
        modelAndView.addObject("customerList", userRepository.findAll());
        modelAndView.setViewName("customerDetails");
        return modelAndView;
    }
    
    
    
    // getters and setters
    public CustomerRepository getUserRepository() {
        return userRepository;
    }

    public void setUserRepository(CustomerRepository userRepository) {
        this.userRepository = userRepository;
    }

    public ConfirmationTokenRepository getConfirmationTokenRepository() {
        return confirmationTokenRepository;
    }

    public void setConfirmationTokenRepository(ConfirmationTokenRepository confirmationTokenRepository) {
        this.confirmationTokenRepository = confirmationTokenRepository;
    }

    public EmailSenderService getEmailSenderService() {
        return emailSenderService;
    }

    public void setEmailSenderService(EmailSenderService emailSenderService) {
        this.emailSenderService = emailSenderService;
    }
    
    
}




Below is the Security Configuration class: SecurityConfig.java

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{

    @Override
    protected void configure(HttpSecurity http) throws Exception {
     
        http
        .authorizeRequests()
        .antMatchers(
            "/Index/**"
            ,"/Category/**"
            ,"/register**" 
            ,"/css/**"
            ,"/fonts/**"
            ,"/icon-fonts/**"
            ,"/images/**"
            ,"/img/**"
            ,"/js/**"
            ,"/Source/**").permitAll()
        .anyRequest().authenticated()
        .and()
        .formLogin()
        .loginPage("/login")
        .permitAll()        
        .and()
        .logout()
        .permitAll();
    
    }
    
}

Below is the Thymeleaf login page: login.html

<html xmlns:th="http://www.thymeleaf.org" xmlns:tiles="http://www.thymeleaf.org">
  <head>
    <title tiles:fragment="title">Login</title>
  </head>
  <body>
    <div tiles:fragment="content">
        <form name="f" th:action="@{/login}" method="post">               
            <fieldset>
                <legend>Please Login</legend>
                <div th:if="${param.error}" class="alert alert-error">    
                    Invalid username and password.
                </div>
                <div th:if="${param.logout}" class="alert alert-success"> 
                    You have been logged out.
                </div>
                <label for="emailId">E-mail</label>
                <input type="text" id="emailId" name="emailId"/>        
                <label for="password">Password</label>
                <input type="password" id="password" name="password"/>    
                <div class="form-actions">
                    <button type="submit" class="btn">Log in</button>
                </div>
            </fieldset>
        </form>
    </div>
  </body>
</html>

Below is the page which I should be redirect to: customerAccount.html

<html lang="en" xmlns="http://www.w3.org/1999/xhtml"
  xmlns:th="http://www.thymeleaf.org">
    <head>
        <title>Welcome</title>
    </head>
    <body>
    <form th:action="@{/customerAccount}" method="post">
        <center>
            <h3 th:inline="text">Welcome [[${#httpServletRequest.remoteUser}]]</h3>
        </center>
        
        <form th:action="@{/logout}" method="post">
            <input type="submit" value="Logout" />
        </form>
    </form>
    </body>
</html> 

EDIT

New UserDetailsService Class:

public class CustomerDetailsService implements UserDetailsService{
    
    @Autowired
    private CustomerRepository customerRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        Customer customer = customerRepository.findByEmailIdIgnoreCase(username);
        if (customer == null) {

            throw new UsernameNotFoundException(username);
        }

        return new MyUserPrincipal(customer);
    }

}

class MyUserPrincipal implements UserDetails {
    private Customer customer;
 
    public MyUserPrincipal(Customer customer) {

        this.customer = customer;
    }

    @SuppressWarnings("unchecked")
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        // TODO Auto-generated method stub
         Authentication auth = SecurityContextHolder.getContext().getAuthentication();
         if (auth != null) {

             return (Collection<GrantedAuthority>) auth.getAuthorities();
         }

        return null;
    }

    @Override
    public String getPassword() {

        return customer.getPassword();
    }

    @Override
    public String getUsername() {

        return customer.getEmailId();
    }

    @Override
    public boolean isAccountNonExpired() {

        return false;
    }

    @Override
    public boolean isAccountNonLocked() {

        return false;
    }

    @Override
    public boolean isCredentialsNonExpired() {

        return false;
    }

    @Override
    public boolean isEnabled() {

        return customer.isEnabled();
    }
}

I added some System.out.print()s and found out my UserAccountController isn't getting accessed. The CustomerDetailsService class is also accessed and the username is passing correctly. How do I connect the controller with this?

like image 615
Rukshan Avatar asked Aug 21 '20 14:08

Rukshan


2 Answers

What I would recommend is using Spring Security. Seeing this is code you will be copying everytimee you make a new application, it's better to clean it up and let the framework duo its work. ;)

First of all, your implementation of WebSecurityConfigurerAdapter

@Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
    private static final String USER_BY_USERNAME_QUERY = "select Email, Wachtwoord, Activatie from users where Email = ?" ;
    private static final String AUTHORITIES_BY_USERNAME_QUERY = "select Email, Toegang from users where Email = ?" ;

    @Autowired
    private AccessDeniedHandler accessDeniedHandler;

    @Autowired
    private PasswordEncoder encoder;

    @Autowired
    private DataSource dataSource;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                    .antMatchers("/", "/public/**", "/css/**", "/js/**", "/font/**", "/img/**", "/scss/**", "/error/**").permitAll()
                    .antMatchers("/mymt/**").hasAnyRole("MEMBER", "ADMIN", "GUEST", "BOARD")
                    .antMatchers("/admin/**").hasAnyRole("ADMIN", "BOARD")
                    .antMatchers("/support/**").hasAnyRole("ADMIN", "BOARD")
                    .anyRequest().authenticated()
                .and()
                .formLogin()
                    .loginPage("/login")
                    .permitAll()
                .and()
                .logout()
                    .permitAll()
                .and()
                .exceptionHandling().accessDeniedHandler(accessDeniedHandler)
                ;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.jdbcAuthentication()
                .passwordEncoder(encoder)
                .dataSource(dataSource)
                .usersByUsernameQuery(USER_BY_USERNAME_QUERY)
                .authoritiesByUsernameQuery(AUTHORITIES_BY_USERNAME_QUERY)
                ;
    }
}

First of all I load in a PasswordEncoder that I defined in my main app as a @Bean method. I use the BCryptPasswordEncoder for password hashing.

I load my DataSource from the memory of my Spring Boot Starter. This one is configured for the database I use as default (my default persistence unit, because I use Spring Data jpa)

For the configuration of the authorization, I firstly disable the Cross Site Referencing, as a safety measure. Then I configure all my paths and tell Spring who can acces, what part of the web-app. In my Database, these roles are written down as ROLE_MEMBER, ROLE_BOARD, ... Spring Security drops the ROLE_ by itself but needs it to be there.

After that I add the formLogin and point it towards the url of /login and permit all roles to acces the login page. I also add some logout functionality and exception handling. You can leave these out if you want.

Then I configure the Authentication. Here I use jdbcAuthentication to login via Database. I give the encoder for the password, the source of my data, and then use the two queries I precompiled that give the user information and the user roles.

That's actually it.

My Controller is very Simple

@Controller
@RequestMapping("/login")
public class BasicLoginController {
    private static final String LOGON = "authentication/login";

    @GetMapping
    public String showLogin() {
        return LOGON;
    }
}

My Main looks like this:

@SpringBootApplication
@EnableGlobalMethodSecurity(securedEnabled = true)
public class MyMT extends SpringBootServletInitializer {
    public static void main(String[] args) {
        ConfigurableApplicationContext ctx = SpringApplication.run(MyMT.class, args);
    }

    @Bean
    public BCryptPasswordEncoder encoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public Logger logger() {
        return Logger.getLogger("main");
    }

}

My HTML page looks like this:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>MyMT Login</title>
    <div th:insert="fragments/header :: header-css" />
</head>
<body>
<div class="bgMask">
    <div th:insert="fragments/header :: nav-header (active='Home')" />


    <main>
            <div class="container">
                <h1>Login</h1>
                <!-- Form -->
                <form class="text-center" style="color: #757575;" method="post">

                    <p>Om toegang te krijgen tot het besloten gedeelte moet je een geldige login voorzien.</p>
                    <div class="row" th:if="${param.error}">
                        <div class="col-md-3"></div>
                        <div class=" col-md-6 alert alert-danger custom-alert">
                            Invalid username and/or password.
                        </div>
                        <div class="col-md-3"></div>
                    </div>
                    <div class="row" th:if="${param.logout}">
                        <div class="col-md-3"></div>
                        <div class="col-md-6 alert alert-info custom-alert">
                            You have been logged out.
                        </div>
                        <div class="col-md-3"></div>
                    </div>

                    <!-- Name -->
                    <div class="md-form mt-3 row">
                        <div class="col-md-3"></div>
                        <div class="col-md-6">
                            <input type="text" id="username" class="form-control" name="username" required>
                            <label for="username">Email</label>
                        </div>
                        <div class="col-md-3"></div>
                    </div>

                    <!-- E-mai -->
                    <div class="md-form row">
                        <div class="col-md-3"></div>
                        <div class="col-md-6">
                            <input type="password" id="password" class="form-control" name="password" required>
                            <label for="password">Password</label>
                        </div>
                        <div class="col-md-3"></div>
                    </div>

                    <!-- Sign in button -->
                    <input type="submit" value="Login" name="login" class="btn btn-outline-info btn-rounded btn-block z-depth-0 my-4 waves-effect" />

                </form>
                <!-- Form -->
            </div>
    </main>

    <div th:insert="fragments/footer :: footer-scripts" />
    <div th:insert="fragments/footer :: footer-impl" />
</div>
</body>
</html>

Here you can see the two input fields with the username and password name.

Thats it for login configuration. You can now use all your controllers without having to add security to it.

In clean code terms. Adding security everywhere is a cross cutting concern. Spring security fixes this by using Aspect Oriented Programming tricks. In other words. It intercepts the HttpRequest and first checks the user. Security automatically starts a session with user information and checks against this session.

I hope the short explanation helped to get your login working. :)

like image 172
Nick MM Avatar answered Nov 07 '22 17:11

Nick MM


.loginPage("/login")

You have declare login path in your Spring Security, so Spring security will intercept your call to that path, and it will never enter your controller.

So your controller, neither

public ModelAndView login(ModelAndView modelAndView, @RequestParam("emailID")String email, @RequestParam("password")String password)

or

 @GetMapping("/login")
    public String login() {
      return "login";
    }

will work.

You need to supply your own Authentication Provider, like this. Or you can use default provider and supply your UserDetailsService implementation, like this.

like image 35
Sam YC Avatar answered Nov 07 '22 18:11

Sam YC