Spring Boot/ Spring Security, login form, password checking
The problem is in your loadUserByUsername
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = repo.findOneByUserName(username);
if(user == null) {
throw new UsernameNotFoundException(username);
}
if(requireActivation && !user.getToken().equals("1")) {
Application.log.error("User [" + username + "] tried to log in, but his account is not activated.");
throw new UsernameNotFoundException(username + " did not activate his account.");
}
httpSession.setAttribute(CURRENT_USER_KEY, user);
List<GrantedAuthority> auth = AuthorityUtils.commaSeparatedStringToAuthorityList(user.getRole());
return new org.springframework.security.core.userdetails.User(user.getUserName(), user.getPassword(), auth);
}
You set the user to the session. Don't do this! Just load the user and return it.
The user is stored in the session automatically and can be looked up like shown in this answer.
I think the reason why the password check doesn't work is that you configured BCryptPasswordEncoder
as password encoder.
Be sure, that the password, you have stored in your User
, is encoded by this encoder.
Otherwise the password check will fail.
To avoid the custom activation checking make your User
class implement UserDetails. If you check the docs there are 4 flags you can set, which will be checked by spring boot.
boolean isAccountNonExpired() // Indicates whether the user's account has expired.
boolean isAccountNonLocked() // Indicates whether the user is locked or unlocked.
boolean isCredentialsNonExpired() // Indicates whether the user's credentials (password) has expired.
boolean isEnabled() // Indicates whether the user is enabled or disabled.
Your implementation of loadUserByUsername
should look something like this. It really should only do what the method name suggests. Lookup the user and throw a UsernameNotFoundException
if you cant find a user with the given username.
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = repo.findOneByUserName(username);
if(user == null) {
throw new UsernameNotFoundException(username);
}
return user;
}
If you don't want to make your 'User' implement 'UserDetails' (to separate the framework and your business logic for example) return the Spring User using this constructor, where you can set these flags. Your implementation could look like this:
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = repo.findOneByUserName(username);
if(user == null) {
throw new UsernameNotFoundException(username);
}
List<GrantedAuthority> auth = AuthorityUtils.commaSeparatedStringToAuthorityList(user.getRole());
return new org.springframework.security.core.userdetails.User(
user.getUserName(),
user.getPassword(),
requireActivation && !user.getToken().equals("1"), // enabled. Use whatever condition you like
true, // accountNonExpired. Use whatever condition you like
true, // credentialsNonExpired. Use whatever condition you like
true, // accountNonLocked. Use whatever condition you like
auth);
}
The password, authorities, activation state and so on are then checked by spring automatically.
Admin
Updated on June 04, 2022Comments
-
Admin almost 2 years
I have a problem with something that is probably very easy, but I don't understand that.
I am not very familiar with Spring Boot, many things happen here automatically. I want to check if someone who write username and password in form is present in database [and his account is activated]. User data is stored in MySQL database configured in application.properties. I want to check if someone with provided username is present in "user" table and check if provided password is equal to user password in database. At the moment I can type any username from database and password can be random (it's obvious to me, because I don't check it anywhere, and weird, because I feel like everything around says it works properly). This sounds so simple to me, but I can't find any proper solution on StackOverflow or tutorials.
My general question is - where and how should I check the password from the login form? Is it done automatically (but it doesn't work somehow), or should I write my custom controller/service/method to do that? If custom controller is needed, then what should be my direction to solve my problem?
At the moment I have no idea where to go. I hope all remaining code related to my problem is pasted here. Thank you in advance for all tips and comments about that.
Code:
ApplicationSecurityAdapter class:
@Configuration @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER) public class ApplicationSecurityAdapter extends WebSecurityConfigurerAdapter { @Autowired private UserService userService; @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/user/register").permitAll() .antMatchers("/user/activate").permitAll() .antMatchers("/user/activation-send").permitAll() .antMatchers("/user/reset-password").permitAll() .antMatchers("/user/reset-password-change").permitAll() .antMatchers("/user/autologin").access("hasRole('ROLE_ADMIN')") .antMatchers("/user/delete").access("hasRole('ROLE_ADMIN')") .antMatchers("/img/**").permitAll() .antMatchers("/images/**").permitAll() .antMatchers("/fonts/**").permitAll() .anyRequest().authenticated() .and() .formLogin().loginPage("/login").failureUrl("/login?error").permitAll() .and() .logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/login").permitAll() // added permitAll() .and() .rememberMe().key(applicationSecret) .tokenValiditySeconds(31536000); } @Override public void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userService).passwordEncoder(new BCryptPasswordEncoder()); }
UserService class:
@Service public class UserService implements UserDetailsService { @Value("${app.user.verification}") // set to YES private Boolean requireActivation; @Value("${app.secret}") // some random stuff private String applicationSecret; @Autowired private UserRepository repo; @Autowired private HttpSession httpSession; public final String CURRENT_USER_KEY = "CURRENT_USER"; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = repo.findOneByUserName(username); if(user == null) { throw new UsernameNotFoundException(username); } if(requireActivation && !user.getToken().equals("1")) { Application.log.error("User [" + username + "] tried to log in, but his account is not activated."); throw new UsernameNotFoundException(username + " did not activate his account."); } httpSession.setAttribute(CURRENT_USER_KEY, user); List<GrantedAuthority> auth = AuthorityUtils.commaSeparatedStringToAuthorityList(user.getRole()); return new org.springframework.security.core.userdetails.User(user.getUserName(), user.getPassword(), auth); } }
UserController:
@Controller // @RequestMapping("/user/*") public class UserController { private Logger log = LoggerFactory.getLogger(UserController.class); @Value("${app.user.verification}") // YES private Boolean requireActivation; @Value("users/") private String userRoot; @Autowired private UserRepository userRepository; @Autowired protected AuthenticationManager authenticationManager; @Autowired private UserService userService; @RequestMapping("/login") public String login(User user) { return "user/login"; } }
Login form:
<div layout:fragment="content"> <form class="form-signin" th:action="@{/login}" th:object="${user}" method="post"> <h2 class="form-signin-heading">LOGIN PANEL</h2> <div class="alert alert-danger" th:if="${param.error}"> Incorrect credentials or account not activated. </div> <input type="text" id="inputUsername" name="username" class="form-control top" placeholder="username goes here..." required="required" autofocus="autofocus"/> <input type="password" id="inputPassword" name="password" class="form-control bottom" placeholder="password goes here..." required="required"/> <div class="checkbox"> <label> <input type="checkbox" name="remember-me"/> Remember me </label> </div> <button class="btn btn-lg btn-primary btn-block" type="submit">Log in</button> </form> </div>