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

Spring Security JWT获取令牌报错400:invalid grant/Bad credentials求助

解决Spring Security获取JWT令牌时的"invalid grant/Bad credentials"错误

我之前排查过不少类似的问题,结合你给出的信息,这个400错误的核心是凭证验证失败,咱们一步步来定位解决:

1. 先确认H2数据库的用户数据是否真的插入成功

你的data.sql里用了user作为表名,但user是H2的保留关键字,直接使用可能导致插入语句静默执行失败,数据库里根本没有用户数据,自然会验证失败。

修正方法:把表名用双引号括起来,修改插入语句:

INSERT INTO "user" (id, username, password) 
VALUES (1, 'joe.bloggs@example.com', '{bcrypt}$2y$10$rHtfxfURj05SoLAwkn9sMO27B9eS7Js0ceh5pyYmrKoyUk.3VPJGi');

启动项目后可以访问H2控制台(默认路径/h2-console),查询"user"表确认数据是否存在。

2. 检查PasswordEncoder的配置是否正确

你数据库里的密码带了{bcrypt}前缀,这是Spring Security 5+的密码格式标记,用来告诉框架用哪种加密算法验证密码。如果你的Security配置里没有正确配置支持这种格式的PasswordEncoder,就会导致验证失败。

正确的配置示例:

@Bean
public PasswordEncoder passwordEncoder() {
    // 自动识别密码前缀对应的加密算法
    return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}

如果你之前用的是new BCryptPasswordEncoder(),它不会识别{bcrypt}前缀,会直接把前缀当成密码的一部分去验证,自然匹配失败。

3. 验证UserDetailsService的实现是否正确

确保你的UserDetailsService在加载用户时,完整返回了数据库里的加密密码,没有做任何额外的处理(比如再次加密)。

正确的实现示例:

@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));
        // 直接返回数据库中的加密密码,不要修改
        return User.withUsername(user.getUsername())
                .password(user.getPassword())
                .roles("USER") // 根据你的实际角色配置调整
                .build();
    }
}

这里要注意:绝对不要对从数据库取出的密码再次调用passwordEncoder.encode(),否则会把已经加密的密码再加密一次,导致完全不匹配。

4. 确认获取令牌的请求参数是否正确

获取JWT令牌的POST请求(通常是/oauth/token或自定义端点)需要传递正确的参数:

  • grant_type:必须是password(如果使用密码模式)
  • username:要和数据库里的joe.bloggs@example.com完全一致(注意大小写、邮箱格式)
  • password:要输入原始明文密码(比如你生成bcrypt密码时用的明文,比如test123),而不是数据库里的加密字符串
  • 如果配置了客户端认证,还要传递client_id和client_secret

比如用Postman请求时,表单参数应该是:

grant_type: password
username: joe.bloggs@example.com
password: 你的原始明文密码
client_id: 你的客户端ID(如果有配置)
client_secret: 你的客户端密钥(如果有配置)

5. 检查安全配置中的认证管理器

确保你的Security配置中,AuthenticationManager正确关联了你的UserDetailsService和PasswordEncoder:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private CustomUserDetailsService userDetailsService;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
                .passwordEncoder(passwordEncoder);
    }

    // 其他配置...比如JWT过滤器、授权规则等
}

按照上面的步骤逐一排查,应该能快速定位问题。我之前遇到过好几次都是H2表名的问题,或者PasswordEncoder没配置对,你可以先从这两个点入手检查。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 04:18:16