将用户名更改为电子邮件后无法进行身份验证。 Spring Boot 3 和 Spring Security 6

问题描述 投票:0回答:1

问题就在标题中。

安全配置

@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

我改变的一切:

  1. 将 .usernameParameter("email") 添加到 SecurityConfig;
  2. UserRepository 中的 findByUsername() 更改为 findByEmail();
  3. CustomUserDetails 的 getUsername() 中的 .getName() 更改为 .getEmail();
  4. CustomUserDetailsService 的 loadUserByUsername() 中将 .findByName() 更改为 .findByEmail()。

我还认为登录页面或控制器可能存在问题,因为我在引导程序上重新制作了它:

登录页面

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

结构如下:

enter image description here

感谢所有提供帮助的人!

spring-boot spring-security spring-data-jpa thymeleaf
1个回答
0
投票

此问题的解决方案是删除此项目中名为“CustomUserDetails”的包装类。

我不确定 Spring Security 如何在身份验证逻辑的基础上工作,但当我使用包装类而不是直接模型 User 实现 UserDetails 时,似乎会出现问题。

因此,只需删除“CustomUserDetails”并直接在模型“User”中实现 UserDetails 就会有所帮助。

© www.soinside.com 2019 - 2024. All rights reserved.