You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot Security凭据正确却返回Bad Credentials问题排查

登录返回Bad Credentials问题排查与解决

问题场景

基于Spring Boot + MySQL的应用,通过POST /api/users 接口添加新用户时,加密后的密码已成功存入数据库,但使用正确凭据登录表单时,返回Bad Credentials错误。

相关代码

SecurityConfig.java

@Configuration
@EnableWebSecurity
public class SecurityConfig
{
    
    @Bean
    public UserDetailsService userDetailsService(DataSource dataSource)
    {
        return new JdbcUserDetailsManager(dataSource);
    }
    @Bean
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception
    {
        http.authorizeHttpRequests()
//                .requestMatchers("/**").permitAll()
                .requestMatchers(HttpMethod.POST, "/api/users").permitAll()
                .and().formLogin().permitAll()
                .and().logout().permitAll()
                .and().cors().and().csrf().disable()
                .httpBasic();
        
        
        return http.build();
    }
    
    @Bean
    public PasswordEncoder passwordEncoder()
    {
        return new BCryptPasswordEncoder();
    }
}

UserController.java

@RestController
@RequestMapping("/api/users")
public class UserController
{
    @Autowired
    private UserRepository userRepository;
    
    @Autowired
    private PasswordEncoder passwordEncoder;
    
    
 @PostMapping()
    public User insertUser(@RequestBody User user)
    {
        if (user.getId() != 0)
        {
            return null;
        }
        
        user.setPassword(passwordEncoder.encode(user.getPassword()));
        return userRepository.save(user);
    }
}

User.java(实体类)

@Entity
@Table(name = "users")
public class User
{
    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    
    @NotEmpty
    @Column(name = "username",nullable = false,unique = true)
    private String username;
    
    @NotEmpty
    @Column(name = "password",nullable = false)
    private String password;
    
    private boolean enabled = true;
    
    @Column(name = "email")
    @Email(message = "Email should be valid")
    private String email;
    
    @Column(name = "address")
    private String address;
    
    @Column(name = "AFM")
    private Long AFM;
    
    @Column(name = "phone_number")
    private Long phone_number;

    public User()
    {
    
    }
    
    public User(String name, String email, String address, Long AFM, Long phone_number)
    {
        this.username = name;
        this.email    = email;
        this.address = address;
        this.AFM = AFM;
        this.phone_number = phone_number;

    }
    

   //Getters Setters

}

创建用户的请求体

{
    "username":"nikos gourn",
    "email":"nikos@gmail.com",
    "address":"kapio allo meros",
    "afm":98776,
    "phone_number":2109017131,
    "password":"123"
}

错误原因分析

1. JdbcUserDetailsManager默认表结构不兼容

你使用的JdbcUserDetailsManager是Spring Security默认的JDBC实现,它强制依赖特定的表结构:

  • 必须存在authorities表存储用户权限/角色,否则无法加载完整的用户认证信息
  • 即使users表包含username、password、enabled字段,缺少权限表会导致认证逻辑异常

2. 密码编码器未关联到认证管理器

虽然你定义了BCryptPasswordEncoder Bean,但JdbcUserDetailsManager没有自动关联该编码器,导致它无法用BCrypt算法验证密码。

解决方案

方案一:适配Spring Security默认表结构

  1. 创建authorities表:
CREATE TABLE authorities (
    username VARCHAR(50) NOT NULL,
    authority VARCHAR(50) NOT NULL,
    FOREIGN KEY (username) REFERENCES users(username)
);
  1. 添加用户时同步插入权限记录(例如给用户默认ROLE_USER权限):
    在UserController的insertUser方法中,新增插入权限的逻辑(可通过JdbcTemplate或直接调用JdbcUserDetailsManager的方法实现)。
  2. 给JdbcUserDetailsManager绑定密码编码器:
    修改SecurityConfig中的Bean定义:
@Bean
public UserDetailsService userDetailsService(DataSource dataSource, PasswordEncoder passwordEncoder) {
    JdbcUserDetailsManager manager = new JdbcUserDetailsManager(dataSource);
    manager.setPasswordEncoder(passwordEncoder);
    return manager;
}

方案二:自定义UserDetailsService(更灵活)

如果不想使用默认表结构,自定义实现UserDetailsService来适配你的users表:

@Service
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username)
                .orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));
        
        // 自定义用户权限,示例为默认ROLE_USER
        return User.withUsername(user.getUsername())
                .password(user.getPassword())
                .roles("USER")
                .enabled(user.isEnabled())
                .build();
    }
}

然后修改SecurityConfig中的UserDetailsService Bean:

@Bean
public UserDetailsService userDetailsService(CustomUserDetailsService customUserDetailsService) {
    return customUserDetailsService;
}

调试流程

  • 验证数据库数据:确认users表中的password字段是BCrypt加密后的字符串(以$2a$开头),而非明文。
  • 开启调试日志:在application.properties中添加配置,查看认证全流程日志:
logging.level.org.springframework.security=DEBUG
  • 断点调试:在loadUserByUsername方法和BCryptPasswordEncoder.matches方法打代码断点,检查用户是否被正确查询、密码匹配逻辑是否正常执行。
  • 手动验证密码匹配:编写测试代码调用passwordEncoder.matches("123", 数据库中的加密密码),确认返回值为true。

内容的提问来源于stack exchange,提问作者Ledrake

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 12:30:44