Простой пример безопасности Spring без входа в систему

#spring-boot #spring-mvc #spring-security #thymeleaf

#spring-boot #spring-mvc #spring-безопасность #thymeleaf

Вопрос:

Доброе утро!

Только начал изучать Spring Security с помощью руководства Baeldung по адресу https://www.baeldung.com/spring-security-authentication-with-a-database .

Однако это не совсем работает. Что я пытаюсь сделать, так это подключить мою простую базу данных H2, содержащую таблицу пользователей с идентификатором, именем пользователя и паролем (для простоты в виде открытого текста), к моему защищенному веб-приложению.

Я создал классы WebSecurityConfig (расширение WebSecurityConfigurerAdapter, см. Ниже), MyUserDetailsService (реализация UserDetailsService, см. Ниже) и loggedInUser (реализация UserDetails, см. Ниже).

WebSecurityConfig: это должно защитить все страницы, кроме домашней, страницы входа и регистрации, которая работает. Кроме того, globalSecurityConfiguration следует включить функцию входа в систему, связавшись с 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: это позволяет внедрить репозиторий для доступа к моей базе данных. Я проверяю базу данных на наличие имени пользователя, и если оно присутствует, я возвращаю новый 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));
    }
}
 

И, наконец, LoggedInUser класс:

 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;
    }
}
 

Когда я пытаюсь войти в систему с каким-либо несуществующим именем пользователя, сообщение об ошибке появляется, как и должно быть. Однако, когда я пытаюсь войти в систему с существующим именем пользователя (с любым паролем, правильным или неправильным), он не выдает никакого сообщения об ошибке, но и не регистрирует меня (по крайней мере, я все еще не могу получить доступ к другим защищенным страницам приложения).

Я опускаю User UserRepository классы and, поскольку они просто довольно просты и хорошо протестированы. Моя страница входа выглядит так:

 <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>
 

Я знаю, что loadUserByUsername метод не касается пароля, но из того, что я прочитал, проверка правильности ввода пароля происходит автоматически в рамках безопасности.

Я также попытался реализовать свой собственный AuthenticationProvider , чтобы использовать вместо UserDetailsService , чтобы проверить, соответствуют ли вводимые данные имени пользователя и пароля записям базы данных в authenticate методе, но затем я столкнулся с другой проблемой — неправильные учетные данные теперь помечаются правильно, но правильные учетные данные выдают ошибку Cannot invoke "Object.toString()" because the return value of "org.springframework.security.core.Authentication.getCredentials()" is null . Однако строка, упомянутая в ошибке, была той, которая считывает пароль из пользовательского ввода — и поскольку это происходит только для паролей, соответствующих правильному, этого не должно быть null . Я не публикую здесь код, поскольку, вероятно, это другая проблема.

Спасибо за любую помощь! Помните, что это похоже на первый раз, когда я коснулся какой-либо структуры безопасности, так что лучше ELI5 🙂