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