Spring Security数据库认证失败求助(authenticate未知源错误)
我跟着Teddy Smith的YouTube教程搭建Spring Security认证系统,已经完成注册功能和PasswordEncoder配置,但用数据库里的用户无法完成认证,触发authenticate(Unknown Source)错误,卡着没法继续学习。以下是相关代码和控制台报错:
UserEntity类
package springsecuritylearn.entity; import java.util.ArrayList; import java.util.List; import jakarta.persistence.CascadeType; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.JoinTable; import jakarta.persistence.ManyToMany; import jakarta.persistence.SequenceGenerator; import jakarta.persistence.Table; @Entity @Table(name = "users") public class UserEntity { @Id @SequenceGenerator(name = "user_sequence", sequenceName = "user_sequence", allocationSize = 1) @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_sequence") private long id; private String username; private String password; @ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL) @JoinTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id")) private List<RoleEntity> roles = new ArrayList<>(); public UserEntity() {} public UserEntity(String username, String password, List<RoleEntity> roles) { this.username = username; this.password = password; this.roles = roles; } public long getId() { return id; } public void setId(long id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public List<RoleEntity> getRoles() { return roles; } public void setRoles(List<RoleEntity> roles) { this.roles = roles; } }
RoleEntity类
package springsecuritylearn.entity; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.SequenceGenerator; import jakarta.persistence.Table; @Entity @Table(name = "roles") public class RoleEntity { @Id @SequenceGenerator(name = "role_sequence", sequenceName = "role_sequence", allocationSize = 1) @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "role_sequence") private long id; private String name; public RoleEntity() {} public RoleEntity(String name) { this.name = name; } 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; } }
AuthController类
package springsecuritylearn.controller; import java.util.Collections; import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import springsecuritylearn.entity.RoleEntity; import springsecuritylearn.entity.UserEntity; import springsecuritylearn.repository.RoleRepository; import springsecuritylearn.repository.UserRepository; @Controller @RequestMapping("/") public class AuthController { private AuthenticationManager authenticationManager; private PasswordEncoder passwordEncoder; private UserRepository userRepository; private RoleRepository roleRepository; @Autowired public AuthController(AuthenticationManager authenticationManager, PasswordEncoder passwordEncoder, UserRepository userRepository, RoleRepository roleRepository) { this.authenticationManager = authenticationManager; this.passwordEncoder = passwordEncoder; this.userRepository = userRepository; this.roleRepository = roleRepository; } @GetMapping("/login") public String login() { return "login"; } @PostMapping("/login") public String userAuthenticate( UserEntity userEntity, @RequestParam String username, @RequestParam String password, Map<String, Object> model) { Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password)); SecurityContextHolder.getContext().setAuthentication(authentication); return "redirect:/home"; } @GetMapping("/register") public String register() { return "register"; } @PostMapping("/register") private String addNewUser( @RequestParam String username, @RequestParam String password, Map<String, Object> model) { if (userRepository.existsByUsername(username)) { return "register"; } RoleEntity roles = roleRepository.findByName("USER").orElse(null); UserEntity user = new UserEntity(username, passwordEncoder.encode(password), Collections.singletonList(roles)); userRepository.save(user); return "redirect:/login"; } }
WebController类
package springsecuritylearn.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @Controller @RequestMapping(path = "/") public class WebController { @GetMapping(path = "/home") public String home() { return "home"; } @GetMapping(path = "/user") public String user() { return "user"; } @GetMapping(path = "/admin") public String admin() { return "admin"; } }
UserRepository接口
package springsecuritylearn.repository; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import springsecuritylearn.entity.UserEntity; @Repository public interface UserRepository extends JpaRepository<UserEntity, Long> { Optional<UserEntity> findByUsername(String username); Boolean existsByUsername(String username); }
RoleRepository接口
package springsecuritylearn.repository; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import springsecuritylearn.entity.RoleEntity; @Repository public interface RoleRepository extends JpaRepository<RoleEntity, Long> { Optional<RoleEntity> findByName(String name); }
CustomUserDetailsService类
package springsecuritylearn.security; import java.util.Collection; import java.util.List; import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; 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 springsecuritylearn.entity.RoleEntity; import springsecuritylearn.entity.UserEntity; import springsecuritylearn.repository.UserRepository; @Service public class CustomUserDetailsService implements UserDetailsService { private UserRepository userRepository; @Autowired public CustomUserDetailsService(UserRepository userRepository) { this.userRepository = userRepository; } public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { UserEntity user = userRepository.findByUsername(username).orElseThrow(() -> new UsernameNotFoundException("Username not found")); return new User(user.getUsername(), user.getPassword(), mapRolesToAuthorities(user.getRoles())); } private Collection<GrantedAuthority> mapRolesToAuthorities(List<RoleEntity> roles) { return roles.stream().map(role -> new SimpleGrantedAuthority(role.getName())).collect(Collectors.toList()); } }
SecurityConfig类
package springsecuritylearn.security; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; @EnableWebSecurity @Configuration public class SecurityConfig { private CustomUserDetailsService userDetailsService; @Autowired public SecurityConfig(CustomUserDetailsService userDetailsService) { this.userDetailsService = userDetailsService; } @Bean public SecurityFilterChain configure(HttpSecurity http) throws Exception { http .csrf().disable() .authorizeHttpRequests() .requestMatchers("/", "/register").permitAll() .requestMatchers("/admin").hasRole("ADMIN") .anyRequest().authenticated() .and() .formLogin() .defaultSuccessUrl("/home") .loginPage("/login") .permitAll() .and() .logout() .permitAll() .and() .httpBasic() .disable(); return http.build(); } @Bean public UserDetailsService users() { UserDetails user1 = User.builder() .username("august") .password("{noop}august") .roles("ADMIN") .build(); UserDetails user2 = User.builder() .username("maksim") .password("{noop}maksim") .roles("USER") .build(); return new InMemoryUserDetailsManager(user1, user2); } @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { return authenticationConfiguration.getAuthenticationManager(); } @Bean PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
控制台报错信息
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343) ~[spring-aop-6.0.7.jar:6.0.7] at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:211) ~[spring-aop-6.0.7.jar:6.0.7] at jdk.proxy7/jdk.proxy7.$Proxy133.authenticate(Unknown Source) ~[na:na] at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[na:na] at java.base/java.lang.reflect.Method.invoke(Method.java:578) ~[na:na]
问题解决步骤
1. 移除内存用户配置
SecurityConfig里同时定义了InMemoryUserDetailsManager和CustomUserDetailsService,Spring会优先使用内存用户源,导致认证时不走数据库。直接删掉users()这个Bean:
// 删掉这段代码 @Bean public UserDetailsService users() { UserDetails user1 = User.builder() .username("august") .password("{noop}august") .roles("ADMIN") .build(); UserDetails user2 = User.builder() .username("maksim") .password("{noop}maksim") .roles("USER") .build(); return new InMemoryUserDetailsManager(user1, user2); }
2. 配置AuthenticationManager使用自定义UserDetailsService
确保AuthenticationManager知道要使用你的CustomUserDetailsService,在SecurityConfig里添加认证提供者配置:
@Bean public AuthenticationProvider authenticationProvider() { DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); authProvider.setUserDetailsService(userDetailsService); authProvider.setPasswordEncoder(passwordEncoder()); return authProvider; } @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception { return authConfig.getAuthenticationManager(); }
然后在SecurityFilterChain里注册这个提供者:
http .csrf().disable() .authorizeHttpRequests() // ... 其他原有配置 .and() .authenticationProvider(authenticationProvider()) // 添加此行 .formLogin() // ... 其他原有配置
3. 角色权限匹配问题
hasRole("ADMIN")会自动给角色名加上ROLE_前缀,但你的CustomUserDetailsService里直接把数据库角色名转成SimpleGrantedAuthority(比如数据库存"USER",权限就是"USER"),这时候hasRole("USER")会找"ROLE_USER",导致权限不匹配。解决方法二选一:
- 数据库里角色名存成
ROLE_USER、ROLE_ADMIN,和hasRole的前缀规则匹配; - 把
hasRole改成hasAuthority,比如requestMatchers("/admin").hasAuthority("ADMIN"),直接匹配数据库里的角色名。
4. 移除自定义的登录POST处理
Spring Security的formLogin已经自动处理了登录请求,你自己写的@PostMapping("/login")会和框架的处理冲突,直接删掉这个方法,让框架自动处理认证流程,包括失败后的跳转和错误提示。
内容的提问来源于stack exchange,提问作者Maksim1251

