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?
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. :)
.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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With