0

I'm trying make a user authenticated after a successful sign-up with spring security and boot. The login and the registration by themselves work fine, but when I sign-up, I'm always redirected to the login page despite me having followed this thread (Auto login after successful registration).

Most solutions online seem to deal with the problem almost the same way, but none of them worked for me.

Here are the relevant classes that might affect my problem:

Signup controller:

import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetails;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.servlet.ModelAndView;
import training.spring.profilemanger.exception.PasswordsNotMatchingException;
import training.spring.profilemanger.exception.UserEmailExistsException;
import training.spring.profilemanger.model.User;
import training.spring.profilemanger.model.UserLogin;
import training.spring.profilemanger.service.UserService;

import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;

@Controller
public class IdentificationController {

    private   UserService           userService;
    protected AuthenticationManager authenticationManager;

    public IdentificationController(UserService userService, AuthenticationManager authenticationManager) {
        this.userService = userService;
        this.authenticationManager = authenticationManager;
    }

    private static final String VIEW_PATH = "identification/";

    @GetMapping({"/login", "/logout"})
    public ModelAndView loginForm(ModelAndView modelAndView) {
        modelAndView.addObject("userLogin", new UserLogin());
        modelAndView.setViewName(VIEW_PATH + "login");
        return modelAndView;
    }

    @GetMapping("/signup")
    public ModelAndView signupForm(ModelAndView modelAndView) {
        modelAndView.addObject("user", new User());
        modelAndView.setViewName(VIEW_PATH + "signup");
        return modelAndView;
    }

    @PostMapping("/signup")
    public ModelAndView signupSubmit(ModelAndView modelAndView, @ModelAttribute @Valid User user,
                                     BindingResult bindingResult, @ModelAttribute("passwordConfirmation") String passwordConfirmation,
                                     HttpServletRequest request) {
        if (bindingResult.hasErrors()) {
            modelAndView.setViewName(VIEW_PATH + "signup");
        } else {
            try {
                userService.validateAllFields(user, passwordConfirmation);
                userService.save(user);
                authenticateUser(user, request);
                modelAndView.addObject("users", userService.findAll());
                modelAndView.setViewName("redirect:/users");
            } catch (PasswordsNotMatchingException e) {
                bindingResult.rejectValue("password", "error.user", "passwords are not matching");
                modelAndView.setViewName(VIEW_PATH + "signup");
            } catch (UserEmailExistsException e) {
                bindingResult.rejectValue("email", "error.user", "email already in use");
                modelAndView.setViewName(VIEW_PATH + "signup");
            }
        }
        return modelAndView;
    }

    private void authenticateUser(User user, HttpServletRequest request) {
        UsernamePasswordAuthenticationToken token =
                new UsernamePasswordAuthenticationToken(user.getEmail(), user.getPassword());

        // generate session if one doesn't exist
        request.getSession();

        token.setDetails(new WebAuthenticationDetails(request));
        Authentication authenticatedUser = authenticationManager.authenticate(token);

        SecurityContextHolder.getContext().setAuthentication(authenticatedUser);

        request.getSession().setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext());
    }
}

WebSecurityConfig

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import training.spring.profilemanger.service.UserService;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private PasswordEncoder    passwordEncoder;
    private UserDetailsService userService;

    public WebSecurityConfig(PasswordEncoder passwordEncoder, UserService userService) {
        this.passwordEncoder = passwordEncoder;
        this.userService = userService;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/login", "/signup").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
                .logout()
                .permitAll();
    }

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

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

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

WebMvcConfig :

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class WebMvcConfig {
    @Bean
    public PasswordEncoder passwordEncoder() {
        PasswordEncoder encoder = new BCryptPasswordEncoder();
        return encoder;
    }
}

UserService:

import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import training.spring.profilemanger.exception.PasswordsNotMatchingException;
import training.spring.profilemanger.exception.UserEmailExistsException;
import training.spring.profilemanger.model.MyUserDetails;
import training.spring.profilemanger.model.User;
import training.spring.profilemanger.repository.UserRepository;

@Service
public class UserService implements UserDetailsService {

    private UserRepository  userRepository;
    private PasswordEncoder passwordEncoder;

    public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder) {
        this.userRepository = userRepository;
        this.passwordEncoder = passwordEncoder;
    }

    public void validateAllFields(User user, String passwordConfirmation) throws PasswordsNotMatchingException, UserEmailExistsException {
        user = trimWhiteSpaces(user);
        checkPasswordsMatching(user.getPassword(), passwordConfirmation);
        if (userRepository.findByEmail(user.getEmail()) != null) {
            throw new UserEmailExistsException();
        }
    }

    public User save(User user) {
        user.setPassword(passwordEncoder.encode(user.getPassword()));
        return userRepository.save(user);
    }

    private User trimWhiteSpaces(User user) {
        user.setFirstName(user.getFirstName().trim());
        user.setLastName(user.getLastName().trim());
        user.setEmail(user.getEmail().trim());
        return user;
    }

    public Iterable<User> findAll() {
        return userRepository.findAll();
    }

    private void checkPasswordsMatching(String password, String passwordConfirmation) throws PasswordsNotMatchingException {
        if (passwordConfirmation == null || !passwordConfirmation.equals(password)) {
            throw new PasswordsNotMatchingException();
        }
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByEmail(username);
        if (user != null) {
            return new MyUserDetails(user);
        } else {
            throw new UsernameNotFoundException(username + " doesn't exist");
        }
    }
}

signup.html:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>User Form</title>
</head>
<body>
<h1>User Sign-up Form:</h1>
<form action="#" th:action="@{/signup}" th:object="${user}" method="post">
    <input type="hidden" th:field="*{id}"/>
    <table>
        <tr>
            <td>First name</td>
            <td>:<input type="text" th:field="*{firstName}"/></td>
            <td><label th:if="${#fields.hasErrors('firstName')}" th:errors="*{firstName}" style="color: red"/></td>
        </tr>
        <tr>
            <td>Last name</td>
            <td>:<input type="text" th:field="*{lastName}"/></td>
            <td><label th:if="${#fields.hasErrors('lastName')}" th:errors="*{lastName}" style="color: red"/></td>
        </tr>
        <tr>
            <td>Email</td>
            <td>:<input type="email" th:field="*{email}"/></td>
            <td><label th:if="${#fields.hasErrors('email')}" th:errors="*{email}" style="color: red"/></td>
        </tr>
        <tr>
            <td>Password</td>
            <td>:<input type="password" th:field="*{password}"/></td>
            <td><label th:if="${#fields.hasErrors('password')}" th:errors="*{password}" style="color: red"/></td>
        </tr>
        <tr>
            <td>Password Confirmation</td>
            <td>:<input type="password" name="passwordConfirmation"/></td>
        </tr>
        <tr>
            <td></td>
            <td><input type="submit" value="Submit"/><input type="reset" value="Reset"/></td>
        </tr>
    </table>
</form>
</body>
</html>

If you think I'm missing something, don't hesitate to ask me to provide it.

I wish you a very nice day.

walid
  • 45
  • 1
  • 7

2 Answers2

0

I figured it out. I added a request.login(username, password) after I validate and save my new user.

@PostMapping("/signup")
public ModelAndView signupSubmit(ModelAndView modelAndView, @ModelAttribute @Valid User user,
                                 BindingResult bindingResult, @ModelAttribute("passwordConfirmation") String passwordConfirmation,
                                 HttpServletRequest request) {
    if (bindingResult.hasErrors()) {
        modelAndView.setViewName(VIEW_PATH + "signup");
    } else {
        try {
            userService.validateAllFields(user, passwordConfirmation);
            String password = user.getPassword();
            userService.save(user);
            request.login(user.getEmail(), password);
            modelAndView.addObject("users", userService.findAll());
            modelAndView.setViewName("redirect:/users");
        } catch (PasswordsNotMatchingException e) {
            bindingResult.rejectValue("password", "error.user", "passwords are not matching");
            modelAndView.setViewName(VIEW_PATH + "signup");
        } catch (UserEmailExistsException e) {
            bindingResult.rejectValue("email", "error.user", "email already in use");
            modelAndView.setViewName(VIEW_PATH + "signup");
        } catch (ServletException e) {
            e.printStackTrace();
            modelAndView.setViewName(VIEW_PATH + "signup");
        }
    }
    return modelAndView;
}

and I removed authenticateUser method from my controller.

walid
  • 45
  • 1
  • 7
-1

The spring UsernamePasswordAuthenticationToken API states:

The principal and credentials should be set with an Object that provides the respective property via its Object.toString() method. The simplest such Object to use is String.

Thus, I think

UsernamePasswordAuthenticationToken token = 
               new UsernamePasswordAuthenticationToken(user.getEmail(), user.getPassword()); 

should be

String username = user.getEmail();
String password = user.getPassword();
UsernamePasswordAuthenticationToken token =
                new UsernamePasswordAuthenticationToken(username, password);
Istiaque Hossain
  • 2,157
  • 1
  • 17
  • 28
  • Thank you Alec for the reply. I've already tried that and it's not working. getEmail and getPassword methods return string objects, so I'm surprised here. I think the problem lay somewhere deep. – walid Jul 26 '19 at 10:51