问题就在标题中。
安全配置
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final UserDetailsService customUserDetailsService;
private final SuccessUserHandler successUserHandler;
@Autowired
public SecurityConfig(UserDetailsService customUserDetailsService, SuccessUserHandler successUserHandler) {
this.customUserDetailsService = customUserDetailsService;
this.successUserHandler = successUserHandler;
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http .csrf(csrf -> csrf.disable())
.authorizeHttpRequests((auth) -> auth
.requestMatchers("/auth/login", "/auth/registration", "/error").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().hasAnyRole("USER", "ADMIN")
)
.formLogin((login) -> login
.loginPage("/auth/login")
.loginProcessingUrl("/process_login")
.usernameParameter("email")
.successHandler(successUserHandler)
.failureUrl("/auth/login?error")
)
.logout(logout -> logout
.logoutSuccessUrl("/auth/login")
.permitAll()
);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setPasswordEncoder(passwordEncoder());
provider.setUserDetailsService(customUserDetailsService);
return provider;
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().requestMatchers("/resouces/**","/static/**","/css/**");
}
}
用户模型
@Entity
@Table(name = "user", uniqueConstraints = @UniqueConstraint(columnNames = {"user_id", "role_id"}))
public class User {
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name")
private String name;
@Column(name = "surname")
private String surname;
@Column(name = "birthYear")
private int birthYear;
@Column(name = "password")
private String password;
@Column(name = "email", unique = true)
private String email;
@ManyToMany(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
@JoinTable(
name = "users_roles",
joinColumns = @JoinColumn(
name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(
name = "role_id", referencedColumnName = "id"))
private Collection<Role> roles;
public User(String name, String surname, int birthYear) {
this.name = name;
this.surname = surname;
this.birthYear = birthYear;
}
public User() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSurname() {
return surname;
}
public void setSurname(String surname) {
this.surname = surname;
}
public int getBirthYear() {
return birthYear;
}
public void setBirthYear(int birthYear) {
this.birthYear = birthYear;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Collection<Role> getRoles() {
return roles;
}
public void setRoles(Collection<Role> roles) {
this.roles = roles;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", surname='" + surname + '\'' +
", birthYear=" + birthYear +
'}';
}
}
用户详情
package task312.demo.securities;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import task312.demo.models.User;
import java.util.Collection;
import java.util.stream.Collectors;
public class CustomUserDetails implements UserDetails {
private User user;
public CustomUserDetails(User user) {
this.user = user;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return user.getRoles().stream()
.map(role -> new SimpleGrantedAuthority(role.getName()))
.collect(Collectors.toList());
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getEmail();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
public User getUser() {
return this.user;
}
}
用户详情服务
@Service
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
@Autowired
public CustomUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
Optional<User> user = userRepository.findByEmail(email);
if (user.isEmpty()) {
throw new UsernameNotFoundException("User not found");
}
return new CustomUserDetails(user.get());
}
}
用户存储库
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
}
用户服务
@Service
@Transactional(readOnly = true)
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
@Autowired
public UserServiceImpl(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public List<User> findAll() {
return userRepository.findAll();
}
@Override
public User findById(Long id) {
Optional<User> foundUser = userRepository.findById(id);
return foundUser.orElseThrow(EntityNotFoundException::new);
}
@Override
@Transactional
public void save(User user) {
userRepository.save(user);
}
@Override
@Transactional
public void update(Long id, User updatedUser) {
updatedUser.setId(id);
userRepository.save(updatedUser);
}
@Override
@Transactional
public void delete(Long id) {
Optional<User> userToDelete = userRepository.findById(id);
// userToDelete.ifPresent(user -> user.getRoles().clear());
userRepository.deleteById(id);
}
@Override
@Transactional
public void deleteAll() {
userRepository.deleteAll();
}
@Override
public User findByUsername(String username) {
Optional<User> user = userRepository.findByEmail(username);
if (user.isPresent()) {
return user.get();
} else {
throw new UsernameNotFoundException(username);
}
}
@Override
public User findByEmail(String email) {
Optional<User> user = userRepository.findByEmail(email);
if (user.isPresent()) {
return user.get();
} else {
throw new UsernameNotFoundException(email);
}
}
@Override
public boolean isUserAdmin(String username) {
Optional<User> user = userRepository.findByEmail(username);
if (user.isPresent()) {
return user.get().getRoles().stream().anyMatch(role -> "ROLE_ADMIN".equals(role.getName()));
} else {
return false;
}
}
}
和SuccessHandler
@Component
public class SuccessUserHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException {
Set<String> roles = AuthorityUtils.authorityListToSet(authentication.getAuthorities());
if (roles.contains("ROLE_ADMIN")) {
httpServletResponse.sendRedirect("/admin");
} else {
httpServletResponse.sendRedirect("/user");
}
}
}
我有这个项目的 2 个版本 - 用户名身份验证和电子邮件身份验证。用户名一工作正常。 具有用户名身份验证的版本 - https://github.com/avevivi/task3.1.2
我改变的一切:
我还认为登录页面或控制器可能存在问题,因为我在引导程序上重新制作了它:
登录页面
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" xmlns:th="http://www.thymeleaf.org">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Bootstrap demo</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
<link rel="stylesheet" th:href="@{/css/login.css}"/>
</head>
<body class="bg-light">
<div class="container">
<div class="justify-content-center">
<div class="info">
<h3>Please sign in</h3>
</div>
<form th:action="@{/process_login}" method="post">
<div class="col-3 mx-auto ">
<div class="mb-1">
<input type="email" class="form-control" id="email" name="email" placeholder="Email">
</div>
<div class="mb-3">
<input type="password" id="inputPassword6" class="form-control" aria-describedby="passwordHelpInline" placeholder="Password">
</div>
<div class="mx-auto">
<button type="submit" class="btn btn-primary w-100 text-center">Sign in</button>
</div>
</div>
</form>
<div th:if="${param.error}" class="text-center mt-3" style="color: red">Wrong username or password</div>
<div class="col-3 mx-auto text-center mt-3">
<a th:href="@{/auth/registration}" class="link-underline link-underline-opacity-0">Registration</a>
<div/>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous"></script>
</body>
</html>
AuthController
@Controller
@RequestMapping("/auth")
public class AuthController {
private final RegistrationService registrationService;
@Autowired
public AuthController(RegistrationService registrationService) {
this.registrationService = registrationService;
}
@GetMapping("/login")
public String getLoginPage() {
return "auth/login";
}
@GetMapping("/registration")
public String getRegistrationPage(@ModelAttribute("user") User user) {
return "auth/registration";
}
@PostMapping("/registration")
public String doRegistration(@ModelAttribute("user") User user) {
try {
registrationService.register(user);
} catch (Exception e) {
e.printStackTrace();
return "auth/registration";
}
return "redirect:/auth/login";
}
}
结构如下:
感谢所有提供帮助的人!
此问题的解决方案是删除此项目中名为“CustomUserDetails”的包装类。
我不确定 Spring Security 如何在身份验证逻辑的基础上工作,但当我使用包装类而不是直接模型 User 实现 UserDetails 时,似乎会出现问题。
因此,只需删除“CustomUserDetails”并直接在模型“User”中实现 UserDetails 就会有所帮助。