Simple Spring Security example not logging in

Morning!

Just started learning Spring Security with the help of the Baeldung tutorial at https://www.baeldung.com/spring-security-authentication-with-a-database.

However, it's not quite working. What I'm trying to do is connecting my simple H2 database, containing a User table with id, username and password (in plaintext for simplicity), with my secured web application.

I created WebSecurityConfig (extending WebSecurityConfigurerAdapter, see below), MyUserDetailsService (implementing UserDetailsService, see below) and LoggedInUser (implementing UserDetails, see below) classes.

WebSecurityConfig: This should secure all pages except home, login and register pages, which is working. Also, globalSecurityConfiguration should enable the login function by linking to the userDetailsService.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
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;

@Configuration
@EnableWebSecurity
@ComponentScan(basePackageClasses = MyUserDetailsService.class)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private MyUserDetailsService userDetailsService;

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

    @Autowired
    public void globalSecurityConfiguration(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService);
    }
}

MyUserDetailsService: This gets the Repository injection to access my database. I check the database for the username, and if it's present, I return a new LoggedInUser.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class MyUserDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) {
        List<User> users = userRepository.findByUsername(username);
        if (users.size() == 0) {
            throw new UsernameNotFoundException(username);
        }
        return new LoggedInUser(users.get(0));
    }
}

And finally, the LoggedInUser class:

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;

public class LoggedInUser implements UserDetails {
    private User user;

    public LoggedInUser(User user) {
        this.user= user;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return AuthorityUtils.createAuthorityList("ROLE_USER");
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getNickname();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

When I try to log in with some nonexistent username, the error message pops as it should. However, when I'm trying to log in with an existing username (with any password, right or wrong), it's not giving any error message but isn't logging me in either (at least I still can't access other secured pages of the app).

I'm omitting User and UserRepository classes since they're just pretty straightforward and well tested. My login page looks like that:

<html xmlns:th="http://www.thymeleaf.org" xmlns:tiles="http://www.thymeleaf.org">
<head>
    <title>Spring Security Example</title>
</head>
<body>
<div class="container">
    <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>
            <div class="form-group">
                <label for="username">Username</label>
                <input type="text" id="username" name="username"/>
            </div>
            <div class="form-group">
                <label for="password">Password</label>
                <input type="password" id="password" name="password"/>
            </div>
            <button type="submit" class="btn btn-primary">Login</button>

        </fieldset>
    </form>
</div>
</body>
</html>

I know that the loadUserByUsername method isn't touching the password, but from what I've read, checking if the right password was entered happens automatically within the Security framework.

I also tried to implement my own AuthenticationProvider to use instead of the UserDetailsService to check if both username and password inputs match the database entries within the authenticate method, but then I encountered another problem - wrong credentials now get flagged right, but right credentials produced an error Cannot invoke "Object.toString()" because the return value of "org.springframework.security.core.Authentication.getCredentials()" is null. However the line the error mentioned was the one that reads the password from the user input - and since this only happens for passwords matching the correct one, this shouldn't be null. I'm not posting code here since probably this is a different issue though.

Thanks for any help! Remember, this is like the first time I touched any security framework, so better ELI5 :)



Read more here: https://stackoverflow.com/questions/65717868/simple-spring-security-example-not-logging-in

Content Attribution

This content was originally published by Noel93 at Recent Questions - Stack Overflow, and is syndicated here via their RSS feed. You can read the original post over there.

%d bloggers like this: